feat: add paged findKeys (memory-bounded iteration) - #981
Conversation
Adds Database#findKeysPaged(key, notKey, {limit, after}) returning up to
`limit` keys sorted ascending. Callers page by passing the last returned
key as `after` on the next call; the final page returns fewer than
`limit` keys.
Implemented natively on mysql (BINARY ranged WHERE + LIMIT) and postgres
(ranged WHERE + LIMIT). Other backends fall back to findKeys + JS-side
slicing via CacheAndBufferLayer — correct for small/embedded datasets,
but defeats the OOM-mitigation purpose for very large keyspaces.
Drives the fix for ether/etherpad#7830, where SessionStore._cleanup()
loaded every sessionstorage:* key into memory at once and OOMed on
decade-old databases with millions of stale sessions.
Bumps minor to 6.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Review Summary by QodoAdd paged findKeys API for memory-bounded iteration
WalkthroughsDescription• Add memory-bounded findKeysPaged() API for paginated key iteration • Native implementations on MySQL and PostgreSQL with cursor-based paging • Fallback to in-memory slicing for backends without native support • Resolves OOM issue in SessionStore cleanup with millions of stale keys Diagramflowchart LR
A["Database#findKeysPaged<br/>key, notKey, limit, after"] --> B{Backend Type}
B -->|MySQL| C["BINARY key > after<br/>ORDER BY BINARY key LIMIT"]
B -->|PostgreSQL| D["key > after<br/>ORDER BY key LIMIT"]
B -->|Other| E["findKeys + JS slice<br/>via CacheAndBufferLayer"]
C --> F["Paginated Results"]
D --> F
E --> F
File Changes1. databases/mysql_db.ts
|
Code Review by Qodo
1.
|
The new tests use `prefix:*` patterns; existing surrealdb findKeys tests carefully avoid `:` because that backend treats it differently in keys. The pre-existing `findKeys works` tests already work around this — match their skip pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The native mysql/postgres paths throw on non-positive/non-integer limits, but the JS-slicing fallback would silently return an empty slice — which hangs the usual `while (page.length === limit)` paging loop and masks misuse. Validate at the wrapper boundary so behaviour is uniform across backends. Addresses Qodo review on #981. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed the missing-limit-validation bug in 6e80e77 — now throws on non-positive/non-integer limits, matching the mysql/postgres native paths so behaviour is uniform. Backend tests (memory + dirty + mysql, 300 cases) still green locally. |
Adds a usage example and the API contract (ascending byte-order, exclusive `after` cursor, empty-page termination, positive-integer limit). Updates the feature-support matrix with a findKeysPaged column flagging native (mysql/maria/postgres) vs fallback (everyone else). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the per-row native/fallback labels in the feature matrix — every backend supports findKeysPaged. Move the SQL-only memory-bounded caveat into the Limitations section where the other findKeys notes live. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The npmpublish.yml workflow was authenticating via a stored NPM_TOKEN secret that recently started failing E404 on PUT — first observed after #981 merged at 6.1.1 (run 26279691349). Same E404 pattern that hit ep_hljs four days ago. Stored publish tokens have two failure modes that this PR removes entirely: 1. They expire / get rotated and silently break automated publishes. 2. They survive in compromised CI logs / forks long enough to be abused. OIDC trusted publishing exchanges a short-lived GitHub-issued id-token for a per-publish credential at the registry. No secret on our side, no expiry, and the resulting publish is attested with `--provenance` so the npm package page shows the signing GHA run. Removes: - "Set publishing config" step that consumed secrets.NPM_TOKEN - "Add package to etherpad organization" step (the access grant is idempotent and was set at initial publish; it doesn't need to run on every release and the OIDC credential isn't authorised for it) Adds: - `permissions: { contents: write, id-token: write }` on the publish job (contents:write was already implicit via GITHUB_TOKEN for the version-bump push; id-token:write is the OIDC enabler) - `registry-url` on setup-node so the auth header lands at npmjs.org - `--no-git-checks` on pnpm publish (skip the dirty-tree guard that would otherwise trip on the just-pushed version-bump commit) - `--provenance` for the signing attestation One-time setup required on https://www.npmjs.com/package/ueberdb2/access before this lands — Trusted Publishers → Add → Provider: GitHub Actions, Org: ether, Repo: ueberDB, Workflow: npmpublish.yml, Environment: blank. Documented at the top of the publish-npm job. Bumps the publish-job pnpm pin from 10 to 11 to match the test job; trusted publishing requires pnpm >= 10.4 either way. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Database#findKeysPaged(key, notKey, {limit, after})returning up tolimitkeys sorted ascending; pages by passing the last returned key asafterBINARY \key` > ? ORDER BY BINARY `key` LIMIT ?) and **postgres** (key > $n ORDER BY key LIMIT $m`)findKeys+ JS slicing viaCacheAndBufferLayer— correct, but defeats the OOM-mitigation benefit for very large keyspacesWhy
Drives the fix for ether/etherpad#7830, where
SessionStore._cleanup()loaded everysessionstorage:*key into memory at once and OOMed on decade-old MariaDB installs with millions of stale sessions. The reporter's heap snapshot pinned retention on_pool → _allConnections → … → _command._rows → keys— the unbounded result set dragged the process out of heap.Test plan
pnpm exec vitest run test/memory test/dirty— 200 passed (exercises the layer fallback)pnpm exec vitest run test/mysql— 100 passed (native paging path, all 4 new findKeysPaged cases green)pnpm exec vitest run test/postgres— 101 passed (native paging path)pnpm run ts-checkcleanpnpm run lintclean🤖 Generated with Claude Code