perf: Compare byte values directly and parse only UUID-shaped values

projectionValuesEqual took a Uint8List (how a bytea column arrives) down
its List branch and compared it byte by byte, and tryUuidValue parsed
every value as a UUID by throwing and catching an error when it was none.
Each byte of a payload thus cost a thrown TypeError, about 18 µs: two
payloads of 2 KB took 36 ms to compare. A sync whose projection loaded a
few thousand rows with payloads kept the server's isolate busy for
minutes, during which it answered no other request and the client's
method stream timed out, so the client sent the same changes again.

Byte values are now compared as bytes, and tryUuidValue only parses what
can hold a UUID (a UuidValue, 16 bytes, a 36-character string), returning
null for anything else without throwing. A UUID compares with its string
and its bytes as before. Longer byte values are no longer read as the
UUID of their first 16 bytes, which made canonicalProjectionValue group
payloads with the same beginning together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-12 13:11:42 +02:00
commit 7eea8c66b0
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,90 @@
import 'dart:typed_data';
import 'package:serverpod_offline_sync/src/database/merge_utils/unique_resolver.dart';
import 'package:test/test.dart';
import 'package:uuid/uuid.dart';
void main() {
group('Given byte values such as a bytea column holds,', () {
final bytes = Uint8List.fromList(List.generate(4096, (i) => i % 251));
test(
'when comparing equal and differing bytes, '
'then they compare by content.',
() {
final same = Uint8List.fromList(bytes);
final changed = Uint8List.fromList(bytes)..[4000] ^= 1;
final shorter = Uint8List.sublistView(bytes, 0, 4095);
expect(projectionValuesEqual(bytes, same), isTrue);
expect(projectionValuesEqual(bytes, changed), isFalse);
expect(projectionValuesEqual(bytes, shorter), isFalse);
expect(projectionValuesEqual(bytes, null), isFalse);
},
);
test(
'when comparing payloads alike in their first 16 bytes, '
'then they are not taken for the same UUID.',
() {
final payload = Uint8List.fromList('{"schemaVersion":1,"a":1}'.codeUnits);
final other = Uint8List.fromList('{"schemaVersion":1,"a":2}'.codeUnits);
expect(tryUuidValue(payload), isNull);
expect(
canonicalProjectionValue(payload),
isNot(canonicalProjectionValue(other)),
);
},
);
test(
'when comparing many payloads of a few kilobytes, '
'then it takes no more than a moment.',
() {
final same = Uint8List.fromList(bytes);
final watch = Stopwatch()..start();
for (var i = 0; i < 1000; i++) {
projectionValuesEqual(bytes, same);
}
// Byte by byte through a failing UUID parse, this took minutes.
expect(watch.elapsed, lessThan(const Duration(seconds: 2)));
},
);
});
group('Given UUIDs in their forms,', () {
final uuid = const Uuid().v7obj();
test(
'when comparing a UUID with its string and its bytes, '
'then they are the same.',
() {
expect(tryUuidValue(uuid.uuid), uuid);
expect(tryUuidValue(Uint8List.fromList(uuid.toBytes())), uuid);
expect(projectionValuesEqual(uuid, uuid.uuid), isTrue);
expect(projectionValuesEqual(uuid.uuid, uuid), isTrue);
expect(
projectionValuesEqual(Uint8List.fromList(uuid.toBytes()), uuid),
isTrue,
);
},
);
test(
'when comparing what is no UUID, '
'then it is none and compares as itself.',
() {
expect(tryUuidValue('not a uuid'), isNull);
expect(tryUuidValue('x' * 36), isNull);
expect(tryUuidValue(42), isNull);
expect(tryUuidValue(DateTime.utc(2026)), isNull);
expect(projectionValuesEqual(42, 42), isTrue);
expect(projectionValuesEqual(42, 43), isFalse);
expect(projectionValuesEqual([1, 'a'], [1, 'a']), isTrue);
expect(projectionValuesEqual({'a': 1}, {'a': 2}), isFalse);
},
);
});
}