Repository navigation
perf(TC-1274): look up a batch read's kv/get and kv/metadata keys in one statement per space - #375
Conversation
…one statement per space invoke_read_only fetched the current_kv row of each kv/get and kv/metadata capability with its own statement. It now fetches them with one statement per space (key = ANY($2) on PostgreSQL, IN lists of at most 500 on SQLite) and answers the capabilities one at a time in their own order from that result, so responses, order, errors and the read-audit record are unchanged. MySQL and single-capability spaces keep the point lookup.
|
APPROVE: see TC-1274 (reviewed exact head aba7e12) |
…kv-read # Conflicts: # CHANGELOG.md
|
APPROVE: see TC-1274 (main-merge only; code +/- lines byte-identical vs aba7e12; one CHANGELOG header overlap, same class Naomi waived on #374; head 4c3cce7) Detail: merged origin/main 50cb05c (1.21.3 so far: #163 pin, #371, #372, #370, #374) into the approved head aba7e12 with a merge commit. The only conflict was CHANGELOG.md. The PR diff against main has the same 5 files. Four files have byte-identical added/removed lines vs aba7e12 (compared with the corrected +/- filter): tinycloud-core/Cargo.toml, tinycloud-core/src/db.rs, tinycloud-node-server/tests/kv_batch_read.rs, tinycloud-node-server/tests/postgres_round_trip_budget.rs (db.rs, Cargo.toml and the budget test auto-merged with main's #370/#374 changes). Single difference, a SUBSET of the reviewed lines: CHANGELOG.md: the |
…shot Budget conflict only: main now measures five flows. With the one-statement auth snapshot the constants are set to the values measured on PostgreSQL (see the PR body).
TC-1274. Ships in the release after 1.21.2. Fix E from the 2026-10-19 CPU analysis (§3, "Node" option 2).
invoke_read_onlylooked up eachkv/getandkv/metadatacapability with its owncurrent_kvstatement, so a 100-keybatchGet/batchHead(the SDK'sbatchRead) was ~100 round trips. It now looks the keys up with one statement per space and answers the capabilities one at a time, in their own order, from that result.Before / after (PostgreSQL, wire-counted)
postgres_round_trip_budget, new flowskv batch get/kv batch head: one/invokeof 100 capabilities over the fixture (98 distinct keys: an invocation's capabilities are a map, so the repeated key collapses). Measured on this branch's base (a742ab8) and on this branch:current_kvstatementskv/get×100, beforekv/get×100, afterkv/metadata×100, beforekv/metadata×100, afterkv/get(unchanged)A 100-key batch now costs what one key costs. (Each statement is two round trips: the statement and sqlx's release ping.) Budgets are 22 for both flows, and the test fails if a batch reads
current_kvin more than one statement. Before/after logs: thekv batchrows of thepostgres_round_trip_budget -- --nocapturereport.Identical responses. The test hashes the two response bodies. On the base commit and on this branch both are byte-identical across all four rounds: get
31e517b31fd4deb4…5f8f1, head10a8a698f0f145c0…aab8e.What does not change
kv/listandcapabilities/readinterleaved in the same invocation, are as before.x-tinycloud-max-response-bytes. Still checked perkv/getcapability, after its content is read, in capability order. The first oversized capability is the one reported (not the largest), with the same413and message, and a value of exactly the limit passes. There is no partial response.TxStoreError::Tx(Db)for the index lookup,StoreReadfor the block read.db.rsafter the loop), and itsinvoked_abilitiesrows per capability are unchanged.read_audit.rsis untouched.has_unmigrated_artifactshas a MySQL exact path). Its behaviour is identical to before.transactpath'skv/get(writes) is not touched.How
SELECT … FROM current_kv WHERE space = $1 AND deleted = false AND key = ANY($2), one text-array parameter, so the statement text is the same for every batch size. Plan checked on a synthetic 1M-row table with the same primary key:Index Scan using current_kv_pkey,Index Condonspaceandkey = ANY(...).key IN (…), chunks of 500 (limit 32766 variables).tinycloud-coreenables sea-orm'spostgres-arrayfeature for the array parameter (Cargo.lockunchanged).kv_index_lookupstage is observed once per batched statement instead of once per key.Tests
kv_batch_read(new, SQLite, through the real node with SDK-signed invocations) andpostgres_round_trip_budget(PostgreSQL) check a 100-key batch get and head against the fixture: present, deleted and never-written keys, a repeated key, capability order, the cross-check of head against get ETags, the oversize cases (first oversized capability wins, exact-limit passes). Both pass unchanged on the base commit (old per-capability path) and on this branch, which is the old/new comparison.db.rs(SQLite, and PostgreSQL whenTINYCLOUD_TEST_POSTGRES_URLis set):*_kv_lookup_matches_point_lookupscompares, for 200 pseudo-random mixes of up to 120 capabilities (two spaces holding the same key names, live / deleted / absent keys, repeated keys,kv/getandkv/metadatamixed, other capabilities mixed in), the batched lookup againstget_kv_entityfor every capability.*_kv_lookup_statement_countspins the statement count per request shape, including 520 keys (2 statements on SQLite, 1 on PostgreSQL) and a lone capability (point lookup). A failed lookup is an error, not a panic.-D warnings,cargo test -p tinycloud-core --no-fail-fast(lib 497 passed) on SQLite and PostgreSQL 16.15 (ICU);cargo test -p tinycloud-nodeall binaries pass (CI andTINYCLOUD_TEST_BLACKHOLEunset: the 4 black-hole tests skip).get+metadatain one request is a route-level 501 (unchanged), so mixing is covered at the core level only. Multi-space invocations are covered at the core level, not end to end.Conflicts with open PRs
/admin/stats) and the sibling index PR feat(TC-1274): seek KV list ranges on a COLLATE "C" index; hold the legacy-artifact fence scan for 60 s #374:CHANGELOG.mdonly; keep both entries.mainonpostgres_round_trip_budget.rs; this PR adds flows and helpers in the same file (BUDGETS, aWindowmethod,round()), so expect to rebase them over it.