Joining on a collection's own key falls back to a full scan unless an explicit index on that field is created
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 68/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Tranquilla
- Stack tecnologico
- typescript
- Ambito
- databases, performance
Direzione di ricerca
Inizia eseguendo il repro.mjs fornito con e senza INDEX=1, quindi traccia il pianificatore dei join usato da createLiveQueryCollection e il predicato eq(p.userId, u.id). La correzione è completata quando un join di uguaglianza indirizzato al campo getKey della collection collegata usa la mappa delle chiavi esistente, evita una scansione completa ed emette alcun avviso sull'indice senza richiedere createIndex.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
- Validated against
@tanstack/[email protected](Node 24.18.0)
Summary
A collection is a keyed map — getKey is mandatory, collection.get(key) is O(1), collection.state is a Map<TKey, T>. But the join planner does not recognise the key as an index. Joining on the key field falls back to Falling back to loading all data, and the mount cost becomes linear in the size of the joined collection unless you manually createIndex((row) => row.id) — an index over the exact field the collection is already keyed by.
This is the FK → PK join, i.e. the most common join shape in any normalized schema, so the fallback is easy to hit and the fix is a redundant index.
Measurements
10 posts inner-joined to N users on users.id, timing preload() of the live query (repro below):
| users | no explicit index | users.createIndex(r => r.id) |
|---|---|---|
| 25,000 | 39.4 ms | 4.3 ms |
| 50,000 | 64.4 ms | — |
| 100,000 | 115.5 ms | — |
| 200,000 | 243.4 ms | 4.0 ms |
Unindexed is linear in collection size; indexed is flat. The query produces 10 rows either way.
The warning does fire and names the field correctly:
[TanStack DB] [users] Join requires an index on "id" for efficient loading. Falling back to loading
all data. Consider creating an index on the collection with collection.createIndex((row) => row.id)
So the planner knows it wants an index on id — it just doesn't know the collection already has one, by construction.
Reproduction
// node repro.mjs -> WITHOUT explicit index on users.id
// INDEX=1 node repro.mjs -> WITH
import {
BTreeIndex, createCollection, createLiveQueryCollection, eq, localOnlyCollectionOptions,
} from '@tanstack/db'
const USERS = Number(process.env.USERS ?? 50_000)
const users = createCollection(localOnlyCollectionOptions({
id: 'users',
getKey: (row) => row.id,
initialData: Array.from({ length: USERS }, (_, i) => ({ id: `u${i}`, name: `name-${i}` })),
}))
const posts = createCollection(localOnlyCollectionOptions({
id: 'posts',
getKey: (row) => row.id,
initialData: Array.from({ length: 10 }, (_, i) => ({ id: `p${i}`, userId: `u${i}` })),
}))
await users.preload()
await posts.preload()
// The collection is ALREADY a keyed map on this exact field:
console.log(users.get('u42')) // O(1), no index declared
if (process.env.INDEX === '1') {
users.createIndex((row) => row.id, { indexType: BTreeIndex })
}
const started = performance.now()
const q = createLiveQueryCollection((qb) =>
qb
.from({ p: posts })
.join({ u: users }, ({ p, u }) => eq(p.userId, u.id), 'inner')
.select(({ p, u }) => ({ id: p.id, name: u.name })),
)
await q.preload()
console.log(`${(performance.now() - started).toFixed(1)}ms, ${q.toArray.length} rows, ${USERS} users`)
Expected
An equality join whose predicate targets the joined collection's own key field should use the existing key map rather than a full scan — no user-declared index required, and no warning.
Notes
- Same behaviour for a join nested inside a correlated
select()subquery (an "include"), where it is more costly because the include is instantiated per parent row. autoIndex: 'eager'+defaultIndexTypeis a workaround, but it opts the collection into auto-indexing every queried field, which is a much broader change than "use the key you already have".- Related but distinct: #1700 (size-based suggestion gated on
autoIndex) and #1494 (join warning naming the wrong collection) are both about warning quality. This one is about the optimisation itself — here the warning is correct and the fallback is real.
- Lingua principale
- TypeScript
- Stelle
- 3.9k
- Fork
- 272
- Merge medio
- 1g 45m
- PR unite (30g)
- 208
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/db
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 5/100
I maintainer di solito rispondono entro 1 giorno
-
browser-db-sqlite-persistence: option to load the wa-sqlite WASM by URL instead of inlined base64Forse già presa @KyleAMathews l’ha presa 1 giorno fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
TanStack/db#2101 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
TanStack/db#1972 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
cleanName keeps a state code that's joined to the word before it (SavannahGa → "Savannah Ga")Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
openwatersio/slackwater-database#281 ·
I maintainer di solito rispondono entro 1 giorno
-
accepted bug via-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
pingdotgg/t3code#18690 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Bayern feature:Application ready web
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
digitalfabrik/entitlementcard#3176 ·
I maintainer di solito rispondono entro 7 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
NanmiCoder/cc-haha#1533 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno