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
2 changed files with 113 additions and 0 deletions

View file

@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:serverpod_database/serverpod_database.dart' show ColumnType; import 'package:serverpod_database/serverpod_database.dart' show ColumnType;
import 'package:serverpod_serialization/serverpod_serialization.dart'; import 'package:serverpod_serialization/serverpod_serialization.dart';
@ -349,6 +351,9 @@ String canonicalProjectionValue(Object? value) {
bool projectionValuesEqual(Object? left, Object? right) { bool projectionValuesEqual(Object? left, Object? right) {
if (left == null || right == null) return left == right; if (left == null || right == null) return left == right;
if (left is String && right is String) return left == right; if (left is String && right is String) return left == right;
// Byte columns (`bytea`) arrive as Uint8List, which is a List: compared
// element by element below, every byte would go through [tryUuidValue].
if (left is Uint8List && right is Uint8List) return _bytesEqual(left, right);
if (left is Map && right is Map) { if (left is Map && right is Map) {
return left.length == right.length && return left.length == right.length &&
left.entries.every( left.entries.every(
@ -373,11 +378,29 @@ bool projectionValuesEqual(Object? left, Object? right) {
} }
/// Parses [value] as a UUID when it is one, otherwise null. /// Parses [value] as a UUID when it is one, otherwise null.
///
/// Only what can hold a UUID is parsed: a [UuidValue], its 16 bytes, or a
/// string as long as one written with dashes. Anything else is no UUID
/// without trying, as this runs for every value compared and a thrown and
/// caught error costs microseconds each.
@internal @internal
UuidValue? tryUuidValue(Object? value) { UuidValue? tryUuidValue(Object? value) {
final mayBeUuid = switch (value) {
UuidValue() || Uint8List(length: 16) || String(length: 36) => true,
_ => false,
};
if (!mayBeUuid) return null;
try { try {
return value.toUuidValue(); return value.toUuidValue();
} on Object { } on Object {
return null; return null;
} }
} }
bool _bytesEqual(Uint8List left, Uint8List right) {
if (left.length != right.length) return false;
for (var index = 0; index < left.length; index++) {
if (left[index] != right[index]) return false;
}
return true;
}

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);
},
);
});
}