2026-05-03 09:09:23 -03:00
|
|
|
import 'utils/benchmark.dart';
|
|
|
|
|
import 'utils/conversion.dart';
|
2026-06-10 22:36:27 -03:00
|
|
|
import 'utils/merge.dart';
|
2026-05-03 09:09:23 -03:00
|
|
|
import 'utils/results.dart';
|
|
|
|
|
import 'utils/runner.dart';
|
2026-09-14 13:39:07 -03:00
|
|
|
import 'utils/space.dart';
|
2026-05-03 09:09:23 -03:00
|
|
|
|
|
|
|
|
Future<void> main(List<String> args) async {
|
|
|
|
|
final runningInCI = args.contains('--ci');
|
|
|
|
|
String? rowCountArg;
|
|
|
|
|
for (final arg in args) {
|
|
|
|
|
if (arg.startsWith('--rows=')) {
|
|
|
|
|
rowCountArg = arg.substring(7);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
final rowCount = rowCountArg == null ? 1000 : int.parse(rowCountArg);
|
|
|
|
|
final rowsString = formatter0.format(rowCount);
|
|
|
|
|
|
|
|
|
|
if (!runningInCI) print('\n${'=' * 60}');
|
|
|
|
|
print('## Performance Benchmark ($rowsString rows)\n');
|
|
|
|
|
print(
|
|
|
|
|
[
|
|
|
|
|
'This benchmark measures the performance impact of using CRDT ',
|
|
|
|
|
'metadata synchronization on database operations.',
|
|
|
|
|
].join(runningInCI ? '' : '\n'),
|
|
|
|
|
);
|
|
|
|
|
if (!runningInCI) print('-' * 60);
|
|
|
|
|
|
|
|
|
|
final benchmarkResults = <BenchmarkResults>[];
|
|
|
|
|
|
|
|
|
|
for (final operation in Operation.values) {
|
|
|
|
|
final baselineResult = await runWithProgress(
|
2026-07-04 21:17:08 -03:00
|
|
|
'Running ${operation.label} benchmark (baseline)',
|
2026-05-03 09:09:23 -03:00
|
|
|
() => TypesTableBenchmark(
|
2026-07-04 21:17:08 -03:00
|
|
|
'${operation.label} (baseline)',
|
2026-05-03 09:09:23 -03:00
|
|
|
crdtEnabled: false,
|
|
|
|
|
operation: operation,
|
|
|
|
|
rowCount: rowCount,
|
2026-05-07 00:17:30 -03:00
|
|
|
).measure(),
|
2026-05-03 09:09:23 -03:00
|
|
|
skipProgress: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final crdtResult = await runWithProgress(
|
2026-07-04 21:17:08 -03:00
|
|
|
'Running ${operation.label} benchmark (CRDT)',
|
2026-05-03 09:09:23 -03:00
|
|
|
() => TypesTableBenchmark(
|
2026-07-04 21:17:08 -03:00
|
|
|
'${operation.label} (CRDT)',
|
2026-05-03 09:09:23 -03:00
|
|
|
crdtEnabled: true,
|
|
|
|
|
operation: operation,
|
|
|
|
|
rowCount: rowCount,
|
2026-05-07 00:17:30 -03:00
|
|
|
).measure(),
|
|
|
|
|
validator: (result) => result > baselineResult,
|
2026-05-03 09:09:23 -03:00
|
|
|
skipProgress: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
benchmarkResults.add(
|
|
|
|
|
BenchmarkResults(
|
|
|
|
|
operation: operation,
|
|
|
|
|
baseline: baselineResult,
|
2026-09-14 13:39:07 -03:00
|
|
|
offlineSync: crdtResult,
|
2026-05-03 09:09:23 -03:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final spuriousBenchmarks = benchmarkResults.where(
|
2026-09-14 13:39:07 -03:00
|
|
|
(result) => result.baseline > result.offlineSync,
|
2026-05-03 09:09:23 -03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (spuriousBenchmarks.isNotEmpty) {
|
|
|
|
|
final leadingText = runningInCI ? '\n> ' : '\n';
|
|
|
|
|
print(
|
|
|
|
|
[
|
|
|
|
|
'$leadingText❌ Baseline time is greater than CRDT time. This is likely due ',
|
|
|
|
|
'to running with a very low number of rows ($rowsString). This is not ',
|
|
|
|
|
' a valid benchmark and the time comparison should be ignored.',
|
|
|
|
|
].join(runningInCI ? '' : '\n'),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (final result in spuriousBenchmarks) {
|
2026-05-07 00:17:30 -03:00
|
|
|
final baselineDelay = result.baseline.toFormattedDuration();
|
2026-09-14 13:39:07 -03:00
|
|
|
final crdtDelay = result.offlineSync.toFormattedDuration();
|
2026-05-03 09:09:23 -03:00
|
|
|
print(
|
2026-07-04 21:17:08 -03:00
|
|
|
' - ${result.operation.label.toUpperCase()}: $baselineDelay > $crdtDelay',
|
2026-05-03 09:09:23 -03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (final result in benchmarkResults) {
|
|
|
|
|
printPerformanceImpact(
|
|
|
|
|
result,
|
|
|
|
|
rowCount: rowCount,
|
|
|
|
|
runningInCI: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-06-18 01:05:24 -03:00
|
|
|
|
|
|
|
|
// Production-shaped CRDT metadata: many users and a crdt_data_rows table
|
|
|
|
|
// much larger than the queried rows, exercising the scoped and unscoped
|
|
|
|
|
// tombstone predicates.
|
2026-09-14 13:39:07 -03:00
|
|
|
const spaceNoiseUsers = 100;
|
|
|
|
|
final spaceNoiseCrdtRows = rowCount * 100;
|
|
|
|
|
final spaceMeasurements = <SpaceMode, SpaceMeasurement>{};
|
|
|
|
|
for (final spaceMode in SpaceMode.values) {
|
|
|
|
|
spaceMeasurements[spaceMode] = await runWithProgress(
|
|
|
|
|
'Running select space benchmark (${spaceMode.name})',
|
|
|
|
|
() => TombstoneSpaceBenchmark(
|
|
|
|
|
'select space (${spaceMode.name})',
|
|
|
|
|
mode: spaceMode,
|
2026-06-18 01:05:24 -03:00
|
|
|
rowCount: rowCount,
|
2026-09-14 13:39:07 -03:00
|
|
|
noiseUsers: spaceNoiseUsers,
|
|
|
|
|
noiseCrdtRows: spaceNoiseCrdtRows,
|
2026-06-18 01:05:24 -03:00
|
|
|
).measure(),
|
|
|
|
|
skipProgress: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-09-14 13:39:07 -03:00
|
|
|
printSpaceImpact(
|
|
|
|
|
SpaceBenchmarkResults(
|
|
|
|
|
baseline: spaceMeasurements[SpaceMode.baseline]!,
|
|
|
|
|
spaceScoped: spaceMeasurements[SpaceMode.spaceScoped]!,
|
|
|
|
|
unscoped: spaceMeasurements[SpaceMode.unscoped]!,
|
|
|
|
|
noiseUsers: spaceNoiseUsers,
|
|
|
|
|
noiseCrdtRows: spaceNoiseCrdtRows,
|
2026-06-18 01:05:24 -03:00
|
|
|
),
|
|
|
|
|
rowCount: rowCount,
|
|
|
|
|
runningInCI: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-07 00:17:30 -03:00
|
|
|
final baselineStorageResult = await runWithProgress(
|
|
|
|
|
'Running storage benchmark (baseline)',
|
|
|
|
|
() => TypesTableStorageBenchmark(
|
|
|
|
|
'storage (baseline)',
|
|
|
|
|
crdtEnabled: false,
|
|
|
|
|
rowCount: rowCount,
|
|
|
|
|
).reportStorage(),
|
|
|
|
|
skipProgress: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
final crdtStorageResult = await runWithProgress(
|
|
|
|
|
'Running storage benchmark (CRDT)',
|
|
|
|
|
() => TypesTableStorageBenchmark(
|
|
|
|
|
'storage (CRDT)',
|
|
|
|
|
crdtEnabled: true,
|
|
|
|
|
rowCount: rowCount,
|
|
|
|
|
).reportStorage(),
|
|
|
|
|
skipProgress: runningInCI,
|
|
|
|
|
);
|
2026-05-03 09:09:23 -03:00
|
|
|
printStorageImpact(
|
2026-05-07 00:17:30 -03:00
|
|
|
StorageBenchmarkResults(
|
|
|
|
|
baseline: baselineStorageResult,
|
2026-09-14 13:39:07 -03:00
|
|
|
offlineSync: crdtStorageResult,
|
2026-05-07 00:17:30 -03:00
|
|
|
),
|
2026-05-03 09:09:23 -03:00
|
|
|
rowCount: rowCount,
|
|
|
|
|
runningInCI: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
|
2026-06-10 22:36:27 -03:00
|
|
|
if (!runningInCI) print('\n${'=' * 60}');
|
|
|
|
|
print('\n## Merge Benchmark ($rowsString changes)\n');
|
|
|
|
|
print(
|
|
|
|
|
[
|
|
|
|
|
'This benchmark measures the time to apply remote CRDT changes to ',
|
|
|
|
|
'the local database through the sync merge path.',
|
|
|
|
|
].join(runningInCI ? '' : '\n'),
|
|
|
|
|
);
|
|
|
|
|
if (!runningInCI) print('-' * 60);
|
|
|
|
|
|
|
|
|
|
final mergeBenchmarks = <MergeScenarioBenchmark>[
|
|
|
|
|
for (final operation in MergeOperation.values)
|
|
|
|
|
TypesMergeBenchmark(
|
|
|
|
|
'merge (${operation.name})',
|
|
|
|
|
operation: operation,
|
|
|
|
|
changeCount: rowCount,
|
|
|
|
|
),
|
|
|
|
|
UniqueMergeBenchmark('merge (unique conflict)', changeCount: rowCount),
|
2026-09-14 10:26:56 -03:00
|
|
|
for (final operation in SetDefaultOperation.values)
|
|
|
|
|
SetDefaultMergeBenchmark(
|
|
|
|
|
'merge (set default ${operation.name})',
|
|
|
|
|
operation: operation,
|
|
|
|
|
pairCount: rowCount < 1 ? 1 : rowCount,
|
|
|
|
|
),
|
2026-06-10 22:36:27 -03:00
|
|
|
for (final operation in FkChainOperation.values)
|
|
|
|
|
FkChainMergeBenchmark(
|
|
|
|
|
'merge (fk chain ${operation.name})',
|
|
|
|
|
operation: operation,
|
|
|
|
|
changeCount: rowCount,
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (final benchmark in mergeBenchmarks) {
|
|
|
|
|
final mergeResult = await runWithProgress(
|
|
|
|
|
'Running ${benchmark.name} benchmark',
|
|
|
|
|
benchmark.measureMerge,
|
|
|
|
|
skipProgress: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
printMergeImpact(
|
|
|
|
|
MergeBenchmarkResults(
|
|
|
|
|
title: benchmark.resultTitle,
|
|
|
|
|
batchDescription: benchmark.batchDescription,
|
|
|
|
|
average: mergeResult.averageMicroseconds,
|
|
|
|
|
averageQueries: mergeResult.averageQueries,
|
2026-09-14 10:26:56 -03:00
|
|
|
averageRowsRead: mergeResult.averageRowsRead,
|
|
|
|
|
averageRowsReadByType: mergeResult.averageRowsReadByType,
|
2026-06-10 22:36:27 -03:00
|
|
|
changeCount: benchmark.changesPerBatch,
|
|
|
|
|
),
|
|
|
|
|
runningInCI: runningInCI,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 09:09:23 -03:00
|
|
|
print('\n${'-' * 60}');
|
|
|
|
|
print('✅ Benchmark complete!\n');
|
|
|
|
|
}
|