Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .releases/1.9/benchmarks/6292.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# #6292 First Mutation Benchmark

## Claim

Zero 1.9 handles the first mutation with uncached server-schema metadata 2.74x faster than Zero 1.8 on this fixture. Median full-request latency fell from 20.973 ms to 7.645 ms, a 63.5% reduction.

This is not a general process-startup claim. The measurement excludes process launch, module loading, fixture creation, and database connection setup. Subsequent mutations normally reuse the cached schema and are outside the claim.

## Provenance

- Zero 1.8: `zero/v1.8.0` at `cdc02598f137ab4e071878f5674fdc716dbbc69d`
- Zero 1.9 change: `67c8fe4c9d9a5673357bb80116c50d968e54c2e2`, the #6292 commit
- Zero 1.9 release target: `1a0095a00ad21c0f156186d9eee833df85275b38`
- No `packages/zero-server` production file changed between the #6292 commit and the release target.
- Latest benchmark harness: `45706aad581b283037ce0b25130de5c1f27ac086`
- Benchmark source SHA-1: `872b0cbe5f61862e53cb8451940ff4664a39b78e`
- Benchmark config SHA-1: `26eeea5e963235dd229b3e205151af18f4c31da1`
- Shared latency recorder SHA-1: `40e63f7de24fe3b0fe4051d9903d03550a6931c2`
- The measured benchmark source and Zero Server runner were byte-identical in both worktrees.

Zero 1.8 and the #6292 commit also differ in CRUD insert, update, and upsert behavior from #6251 and #6280. The benchmark uses a no-op custom mutation and does not execute those CRUD operations; the measured production-path difference is server-schema introspection.

## Method

- PostgreSQL 17 with a non-owner application role.
- Fixture: 517 application tables and 8,762 columns; the Zero schema selects 75 tables and 1,306 columns.
- One warmed postgres.js connection per run with `max: 1` and `prepare: false`.
- A new database provider per sample resets the `CRUDMutatorFactory` server-schema cache.
- The timed region is the complete `handleMutateRequest()` call for a successful no-op custom mutation.
- Ten fresh Vitest processes per ref, alternated by version to reduce temporal bias.
- Each process ran five warmups followed by 50 measured requests.
- The release metric is the median of the ten process-level medians. Individual request samples are not pooled for the claim.

## Results

| Run | Zero 1.8 median (ms) | Zero 1.9 median (ms) |
| ---------: | -------------------: | -------------------: |
| 1 | 21.792 | 9.005 |
| 2 | 20.192 | 8.026 |
| 3 | 20.188 | 6.455 |
| 4 | 23.361 | 7.731 |
| 5 | 20.448 | 8.207 |
| 6 | 23.372 | 7.560 |
| 7 | 21.337 | 10.019 |
| 8 | 20.609 | 7.344 |
| 9 | 25.217 | 7.272 |
| 10 | 20.263 | 6.491 |
| **Median** | **20.973** | **7.645** |

- Speedup: 2.743x
- Latency reduction: 63.546%
- Zero 1.8 run-median range: 20.188-25.217 ms
- Zero 1.9 run-median range: 6.455-10.019 ms
- Pooled-sample medians, used only as a cross-check: 21.401 ms and 7.606 ms
- Cached-schema mutation control: 0.936 ms and 0.934 ms, effectively flat.
- Warmed-query control: inconclusive because the result reversed with execution order.

## Host Conditions

- Apple M5 Pro, 15 cores, 24 GB memory
- Time Machine inactive before and after the run
- Memory-pressure free percentage: 37% before and 34% after
- 90 CPU samples: 55.3% mean idle, 55.7% median idle, 34.3% minimum idle
- No thermal or performance warning before or after

Raw benchmark samples and host telemetry are preserved at `/var/folders/97/c3gvpw6d46g3nm0y2684_cfm0000gn/T/opencode/zero-server-first-request-45706aad5/`.
333 changes: 333 additions & 0 deletions .releases/1.9/commits.md

Large diffs are not rendered by default.

1,476 changes: 820 additions & 656 deletions assets/search-index.json

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions contents/docs/connecting-to-postgres.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ After your server restarts, show the `wal_level` again to ensure it has changed:
psql -c 'SHOW wal_level'
```

### Socket Inactivity Timeout

`zero-cache` monitors wire activity on its Postgres connections so it can recover when a proxy or network failure leaves a half-open socket. The watchdog samples each connection every 120,000 milliseconds and resets it after one to two intervals without any bytes read or written. In-flight queries on a reset connection are rejected and can recover through their normal retry or restart paths.

Wire activity resets the watchdog, so streaming operations such as `COPY` remain active. A statement that legitimately computes without sending any data for several minutes can be interrupted.

### WAL Sender Timeout

`zero-cache` uses Postgres's `wal_sender_timeout` setting to monitor its replication connection. When the timeout is greater than `0`, Zero sends keepalives and reconnects if the replication stream stops responding.

Setting `wal_sender_timeout` to `0` disables the timeout in Postgres and the related keepalive and reconnect checks in Zero. Other connection failure detection remains active.

### Bounding WAL Size

For development databases, you can set a `max_slot_wal_keep_size` value in Postgres. This will help limit the amount of WAL kept around.
Expand Down
4 changes: 2 additions & 2 deletions contents/docs/connection.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ Reads are allowed while `disconnected`, but writes are rejected and return an of

### Error

If `zero-cache` itself crashes, or if the [mutate](/docs/mutators) or [query](/docs/queries) endpoints return a network or HTTP error, Zero transitions to the `error` state.
If `zero-cache` crashes, or [mutate](/docs/mutators) or [query](/docs/queries) endpoints fail, Zero enters the `error` state. If the response code is `5xx`, `zero-cache` will retry up to four times.

This type of error is unlikely to resolve just by retrying, so Zero doesn't try. The app can retry the connection manually by calling `zero.connection.connect()`.
Zero does not retry from the `error` state. Call `zero.connection.connect()` to retry manually.

Reads are allowed while in the `error` state, but writes are rejected.

Expand Down
4 changes: 3 additions & 1 deletion contents/docs/mutators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ tx.mutate.user.insert({
})
```

If the Zero primary key already exists, `insert` will succeed without changing the row - use `upsert` to update an existing row.

Optional fields can be set to `null` to explicitly set the new field to `null`. They can also be set to `undefined` to take the default value (which is often `null` but can also be some generated value server-side):

```tsx
Expand Down Expand Up @@ -856,7 +858,7 @@ app.post('/api/zero/mutate', async c => {

</CodeGroup>

If Zero receives any response from the mutate endpoint other than HTTP 200, 401, or 403, it will disconnect and enter the [error state](/docs/connection#error).
Responses other than 200, 401, or 403 enter the [error state](/docs/connection#error). `zero-cache` will retry on `5xx` up to four times before returning an error.

If Zero receives HTTP 401 or 403, the client will enter the needs auth state and require a manual reconnect. Use `zero.connection.connect()` for cookie auth or `zero.connection.connect({auth: newToken})` for token auth, then Zero will retry all queued mutations.

Expand Down
Loading
Loading