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

@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:serverpod_database/serverpod_database.dart' show ColumnType;
import 'package:serverpod_serialization/serverpod_serialization.dart';
@ -349,6 +351,9 @@ String canonicalProjectionValue(Object? value) {
bool projectionValuesEqual(Object? left, Object? right) {
if (left == null || right == null) 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) {
return left.length == right.length &&
left.entries.every(
@ -373,11 +378,29 @@ bool projectionValuesEqual(Object? left, Object? right) {
}
/// 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
UuidValue? tryUuidValue(Object? value) {
final mayBeUuid = switch (value) {
UuidValue() || Uint8List(length: 16) || String(length: 36) => true,
_ => false,
};
if (!mayBeUuid) return null;
try {
return value.toUuidValue();
} on Object {
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;
}