test: bench inserts
This commit is contained in:
232
mobile-v2/test/benchmark_insert_test.dart
Normal file
232
mobile-v2/test/benchmark_insert_test.dart
Normal file
@@ -0,0 +1,232 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
// ignore: import_rule_drift
|
||||
import 'package:drift/drift.dart';
|
||||
// ignore: import_rule_drift
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:faker/faker.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||
import 'package:immich_mobile/domain/repositories/asset.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/asset_isar.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
// import 'package:isar/isar.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
import 'test_utils.dart';
|
||||
|
||||
List<Asset> _generateAssets(int count) {
|
||||
final assets = <Asset>[];
|
||||
final faker = Faker();
|
||||
for (int i = 0; i < count; i++) {
|
||||
assets.add(Asset(
|
||||
id: i,
|
||||
name: 'Asset $i',
|
||||
hash: faker.guid.guid(),
|
||||
height: faker.randomGenerator.integer(1000),
|
||||
width: faker.randomGenerator.integer(1000),
|
||||
type: faker.randomGenerator.element(AssetType.values),
|
||||
createdTime: faker.date.dateTime(),
|
||||
modifiedTime: faker.date.dateTime(),
|
||||
duration: faker.randomGenerator.integer(100),
|
||||
localId: faker.guid.guid(),
|
||||
remoteId: faker.guid.guid(),
|
||||
livePhotoVideoId: faker.guid.guid(),
|
||||
));
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
Future<void> _benchDriftInsertsBatched({
|
||||
required String name,
|
||||
required Iterable<List<Asset>> assets,
|
||||
required DriftDatabaseRepository db,
|
||||
}) async {
|
||||
final repo = AssetRepository(db: db);
|
||||
final sp = Stopwatch()..start();
|
||||
for (final chunk in assets) {
|
||||
await repo.upsertAll(chunk);
|
||||
}
|
||||
print('$name - ${sp.elapsed}');
|
||||
}
|
||||
|
||||
Future<void> _benchIsarInsertsBatched({
|
||||
required String name,
|
||||
required Iterable<List<Asset>> assets,
|
||||
required Isar db,
|
||||
}) async {
|
||||
final repo = AssetIsarRepository(db: db);
|
||||
final sp = Stopwatch()..start();
|
||||
for (final chunk in assets) {
|
||||
await repo.upsertAll(chunk);
|
||||
}
|
||||
print('$name - ${sp.elapsed}');
|
||||
}
|
||||
|
||||
Future<void> _benchDriftInsertsNonBatched({
|
||||
required String name,
|
||||
required List<Asset> assets,
|
||||
required DriftDatabaseRepository db,
|
||||
}) async {
|
||||
final repo = AssetRepository(db: db);
|
||||
final sp = Stopwatch()..start();
|
||||
for (final chunk in assets) {
|
||||
await repo.upsert(chunk);
|
||||
}
|
||||
print('$name - ${sp.elapsed}');
|
||||
}
|
||||
|
||||
Future<void> _benchIsarInsertsNonBatched({
|
||||
required String name,
|
||||
required List<Asset> assets,
|
||||
required Isar db,
|
||||
}) async {
|
||||
final repo = AssetIsarRepository(db: db);
|
||||
final sp = Stopwatch()..start();
|
||||
for (final chunk in assets) {
|
||||
await repo.upsert(chunk);
|
||||
}
|
||||
print('$name - ${sp.elapsed}');
|
||||
}
|
||||
|
||||
Future<void> _cleanup(File file) async {
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
late DriftDatabaseRepository drift;
|
||||
late Isar isar;
|
||||
|
||||
// ignore: avoid-local-functions
|
||||
Future<void> setup() async {
|
||||
drift = DriftDatabaseRepository(LazyDatabase(() {
|
||||
sqlite3.tempDirectory = 'test/';
|
||||
return NativeDatabase.createInBackground(File('test/test.sqlite'));
|
||||
}));
|
||||
isar = await TestUtils.initIsar();
|
||||
}
|
||||
|
||||
// ignore: avoid-local-functions
|
||||
Future<void> cleanupFiles() async {
|
||||
await _cleanup(File('test/test.sqlite'));
|
||||
await _cleanup(File('test/test.sqlite-shm'));
|
||||
await _cleanup(File('test/test.sqlite-wal'));
|
||||
await _cleanup(File('test/default.isar'));
|
||||
}
|
||||
|
||||
// ignore: avoid-local-functions
|
||||
Future<void> closeDb() async {
|
||||
await drift.close();
|
||||
if (isar.isOpen) {
|
||||
await isar.close();
|
||||
}
|
||||
}
|
||||
|
||||
test('10K assets', () async {
|
||||
await cleanupFiles();
|
||||
await setup();
|
||||
|
||||
final assets = _generateAssets(10000);
|
||||
await _benchDriftInsertsBatched(
|
||||
name: 'Drift 10K assets batched - 1K slice',
|
||||
assets: assets.slices(1000),
|
||||
db: drift,
|
||||
);
|
||||
await _benchIsarInsertsBatched(
|
||||
name: 'Isar 10K assets batched - 1K slice',
|
||||
assets: assets.slices(1000),
|
||||
db: isar,
|
||||
);
|
||||
|
||||
await closeDb();
|
||||
await cleanupFiles();
|
||||
await setup();
|
||||
|
||||
await _benchDriftInsertsNonBatched(
|
||||
name: 'Drift 10K assets non-batched',
|
||||
assets: assets,
|
||||
db: drift,
|
||||
);
|
||||
await _benchIsarInsertsNonBatched(
|
||||
name: 'Isar 10K assets non-batched',
|
||||
assets: assets,
|
||||
db: isar,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'100K assets',
|
||||
() async {
|
||||
await cleanupFiles();
|
||||
await setup();
|
||||
|
||||
final assets = _generateAssets(100000);
|
||||
await _benchDriftInsertsBatched(
|
||||
name: 'Drift 100K assets batched - 10K slice',
|
||||
assets: assets.slices(10000),
|
||||
db: drift,
|
||||
);
|
||||
await _benchIsarInsertsBatched(
|
||||
name: 'Isar 100K assets batched - 10K slice',
|
||||
assets: assets.slices(10000),
|
||||
db: isar,
|
||||
);
|
||||
|
||||
await closeDb();
|
||||
await cleanupFiles();
|
||||
await setup();
|
||||
|
||||
await _benchDriftInsertsNonBatched(
|
||||
name: 'Drift 100K assets non-batched',
|
||||
assets: assets,
|
||||
db: drift,
|
||||
);
|
||||
await _benchIsarInsertsNonBatched(
|
||||
name: 'Isar 100K assets non-batched',
|
||||
assets: assets,
|
||||
db: isar,
|
||||
);
|
||||
},
|
||||
timeout: Timeout(Duration(minutes: 5)),
|
||||
);
|
||||
|
||||
test(
|
||||
'1M assets',
|
||||
() async {
|
||||
await cleanupFiles();
|
||||
await setup();
|
||||
|
||||
final assets = _generateAssets(1000000);
|
||||
await _benchDriftInsertsBatched(
|
||||
name: 'Drift 1M assets batched - 10K slice',
|
||||
assets: assets.slices(10000),
|
||||
db: drift,
|
||||
);
|
||||
await _benchIsarInsertsBatched(
|
||||
name: 'Isar 1M assets batched - 10K slice',
|
||||
assets: assets.slices(10000),
|
||||
db: isar,
|
||||
);
|
||||
|
||||
await closeDb();
|
||||
await cleanupFiles();
|
||||
await setup();
|
||||
|
||||
await _benchDriftInsertsNonBatched(
|
||||
name: 'Drift 1M assets non-batched',
|
||||
assets: assets,
|
||||
db: drift,
|
||||
);
|
||||
await _benchIsarInsertsNonBatched(
|
||||
name: 'Isar 1M assets non-batched',
|
||||
assets: assets,
|
||||
db: isar,
|
||||
);
|
||||
},
|
||||
timeout: Timeout(Duration(minutes: 25)),
|
||||
);
|
||||
}
|
||||
119
mobile-v2/test/benchmark_query_test.dart
Normal file
119
mobile-v2/test/benchmark_query_test.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'dart:io';
|
||||
|
||||
// ignore: import_rule_drift
|
||||
import 'package:benchmarking/benchmarking.dart';
|
||||
// ignore: import_rule_drift
|
||||
import 'package:drift/drift.dart';
|
||||
// ignore: import_rule_drift
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:faker/faker.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/renderlist.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||
import 'package:immich_mobile/domain/repositories/asset.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||
import 'package:immich_mobile/domain/repositories/renderlist.repository.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
List<Asset> _generateAssets(int count) {
|
||||
final assets = <Asset>[];
|
||||
final faker = Faker();
|
||||
for (int i = 0; i < count; i++) {
|
||||
assets.add(Asset(
|
||||
id: i,
|
||||
name: 'Asset $i',
|
||||
hash: faker.guid.guid(),
|
||||
height: faker.randomGenerator.integer(1000),
|
||||
width: faker.randomGenerator.integer(1000),
|
||||
type: faker.randomGenerator.element(AssetType.values),
|
||||
createdTime: faker.date.dateTime(minYear: 2010, maxYear: 2024),
|
||||
modifiedTime: faker.date.dateTime(minYear: 2010, maxYear: 2024),
|
||||
duration: faker.randomGenerator.integer(100),
|
||||
localId: faker.guid.guid(),
|
||||
remoteId: faker.guid.guid(),
|
||||
livePhotoVideoId: faker.guid.guid(),
|
||||
));
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
Future<void> _driftInserts({
|
||||
required List<Asset> assets,
|
||||
required DriftDatabaseRepository db,
|
||||
}) async {
|
||||
final repo = AssetRepository(db: db);
|
||||
await repo.upsertAll(assets);
|
||||
}
|
||||
|
||||
void main() {
|
||||
late DriftDatabaseRepository drift;
|
||||
late File file;
|
||||
|
||||
setUp(() {
|
||||
file = File('test/test.sqlite');
|
||||
drift = DriftDatabaseRepository(LazyDatabase(() {
|
||||
sqlite3.tempDirectory = 'test/';
|
||||
return NativeDatabase.createInBackground(file);
|
||||
}));
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
for (final table in drift.allTables) {
|
||||
await drift.delete(table).go();
|
||||
}
|
||||
await drift.close();
|
||||
});
|
||||
|
||||
group('Generate RenderList', () {
|
||||
test('10K assets', () async {
|
||||
List<Asset> assets = [];
|
||||
final IRenderListRepository repo = RenderListRepository(db: drift);
|
||||
final result = await asyncBenchmark(
|
||||
'Generate RenderList for 10K assets',
|
||||
() async {
|
||||
await repo.getAll();
|
||||
},
|
||||
setup: () async {
|
||||
assets = _generateAssets(10000);
|
||||
await _driftInserts(assets: assets, db: drift);
|
||||
},
|
||||
);
|
||||
result.report(units: 10000);
|
||||
});
|
||||
test('Drift 100K assets', () async {
|
||||
List<Asset> assets = [];
|
||||
await _driftInserts(assets: assets, db: drift);
|
||||
final IRenderListRepository repo = RenderListRepository(db: drift);
|
||||
final result = await asyncBenchmark(
|
||||
'Generate RenderList for 100K assets',
|
||||
() async {
|
||||
await repo.getAll();
|
||||
},
|
||||
setup: () async {
|
||||
assets = _generateAssets(100000);
|
||||
await _driftInserts(assets: assets, db: drift);
|
||||
},
|
||||
);
|
||||
result.report(units: 100000);
|
||||
});
|
||||
test(
|
||||
'Drift 1M assets',
|
||||
() async {
|
||||
List<Asset> assets = [];
|
||||
final IRenderListRepository repo = RenderListRepository(db: drift);
|
||||
final result = await asyncBenchmark(
|
||||
'Generate RenderList for 1M assets',
|
||||
() async {
|
||||
await repo.getAll();
|
||||
},
|
||||
setup: () async {
|
||||
assets = _generateAssets(1000000);
|
||||
await _driftInserts(assets: assets, db: drift);
|
||||
},
|
||||
);
|
||||
result.report(units: 1000000);
|
||||
},
|
||||
timeout: Timeout(Duration(minutes: 2)),
|
||||
);
|
||||
});
|
||||
}
|
||||
34
mobile-v2/test/test_utils.dart
Normal file
34
mobile-v2/test/test_utils.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/entities/asset_isar.entity.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
abstract final class TestUtils {
|
||||
const TestUtils._();
|
||||
|
||||
/// Downloads Isar binaries (if required) and initializes a new Isar db
|
||||
static Future<Isar> initIsar() async {
|
||||
await Isar.initializeIsarCore(download: true);
|
||||
|
||||
final instance = Isar.getInstance();
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
final db = await Isar.open(
|
||||
[AssetSchema],
|
||||
directory: "test/",
|
||||
maxSizeMiB: 1024,
|
||||
inspector: false,
|
||||
);
|
||||
|
||||
// Clear and close db on test end
|
||||
addTearDown(() async {
|
||||
if (!db.isOpen) {
|
||||
return;
|
||||
}
|
||||
await db.writeTxn(() async => await db.clear());
|
||||
await db.close();
|
||||
});
|
||||
return db;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user