Repository navigation
v0.92.0
Nested reads are faster, and four CLI fixes. A findMany with a with
decodes its relation rows in one pass, which takes the 50-users-with-posts
read from 1.86x to 1.33x of a hand-written query on the benchmark machine.
turbine migrate now takes its lock through the adapter in your config,
turbine observe reads --metrics-url, and two help screens state the
right numbers. No query changes its SQL or its result.
Fixed
turbine migrateuses the configuredadapter.migrate up,
deployanddownnever passed the config file'sadapterto the
migration runner, so a CockroachDB or YugabyteDB project still took
pg_try_advisory_lock(), which on those engines does not keep two
concurrent runs apart. The lock now goes through the adapter's lock table.turbine observe --metrics-urlworks. The flag was accepted and
ignored, soobserverefused to start unlessTURBINE_OBSERVE_URLwas set.
It now reads--metrics-urlfirst, thenTURBINE_OBSERVE_URL.turbine mcp --helpsays eleven tools, the number the server lists. A
test now compares the two.turbine doctor --helpnames both pooler ports it refuses, 6543 and
6432. A test now checks every refused port appears.
Changed
- Studio and the observe dashboard use the brand blue,
#6f9bf7, in
place of#60a5fa. - Bundle-size limits move by 1 kB: the main entry is held under 149 kB
brotli and the edge entry under 117 kB. The growth is the one-pass decoder
below.
Performance
- Nested rows decode in one pass. Under the positional encoding (the
PostgreSQL default), each relation row used to be built three times: as a
keyed object, as a copy with its::textcolumns decoded, and as the parsed
row, which also cost anObject.keysper row to find its decode plan. The
row parser now compiles each relation shape into a plan once per query and
writes every nested row exactly once. The plan reads the same field names,
cell kinds, wire rules and cell decodes the general path does, and a query
whose shapes it cannot prove identical (two columns mapping to one field, a
field named like a relation, an integer-like orObject.prototypekey, a
_count__field) takes the general path unchanged. Rows are identical on
every strategy and encoding; a differential test holds the plan to the
general path over seeded random shapes and payloads, malformed ones
included, on three client configurations, and against real rows with BC
dates, five-digit years and infinity. timestamptzvalues skip the regex work. A value ending in a
±HH:MMoffset, which is how everytimestamptzreaches a nested row, is
now handed straight tonew Date. The general path gave the same answer for
that shape after one regex test and two replaces that could not match, so
the result is unchanged; BC and five-digit years still take the general
parser.
Measured
On the dedicated benchmark machine (benchmarks.yml: a 4-vCPU Firecracker VM
on an AMD Ryzen 9 7950X host, PostgreSQL 17 over a Unix socket, three runs),
against the same workflow on 0.91.0:
| Scenario | 0.91.0 | 0.92.0 |
|---|---|---|
findMany, 50 users + posts |
1.470 ms (1.86x hand-written) | 1.072 ms (1.33x) |
findMany, 10 users + posts + comments |
0.795 ms | 0.636 ms |
| to-one, 500 posts + author | 1.774 ms | 1.511 ms |
| many-to-many, 200 posts + tags | 1.642 ms | 1.490 ms |
| overhead over hand-written SQL, geomean of 8 | 1.141x | 1.100x |
Paired and interleaved in one process (this build against 0.91.0,
plus a second copy of 0.91.0 as the negative control), 600 rounds, local
PostgreSQL 17, medians:
| Scenario | 0.91.0 | This build | Paired change |
|---|---|---|---|
findMany, 50 users + posts |
1.605 ms | 1.376 ms | -0.229 ms (-14%) |
| its row decode alone | 0.441 ms | 0.178 ms | -0.263 ms (-60%) |
findMany, 50 posts + to-one user |
0.188 ms | 0.172 ms | -0.017 ms (-8%) |
findMany, 50 users, no relations |
0.071 ms | 0.070 ms | none |
Against a hand-written query that decodes the same rows to objects and Dates,
the nested read went from 1.39x to 1.19x on this machine. The ::text casts on
int8 / numeric relation columns stay: they are what keeps a value past
2^53 exact.