-
Notifications
You must be signed in to change notification settings - Fork 431
fix(firestore-vector-search): restore batched backfill #3097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: kits
Are you sure you want to change the base?
Changes from all commits
6294c27
a6c430d
940bab9
ac55b20
4b2f759
395d746
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,9 +150,9 @@ This kit is version 0.1.3 of the extension repackaged as an npm package, and it | |
| the least literal of the ports. The seven functions, the Firestore vector index, | ||
| the query document collection and the callable all survive with their names and | ||
| settings intact, so a `.env` copied from your installed instance needs no value | ||
| changes. Multimodal embedding, the backfill, and the shape of the status field | ||
| written onto your documents all changed, so read this before you point the kit at | ||
| a collection an installed instance has already embedded. | ||
| changes. Multimodal embedding and the shape of the status field written onto | ||
| your documents both changed, so read this before you point the kit at a | ||
| collection an installed instance has already embedded. | ||
|
|
||
| ### `EMBEDDING_PROVIDER: multimodal` is not implemented | ||
|
|
||
|
|
@@ -178,8 +178,9 @@ the extension's behaviour, because matching it means writing nothing at all. | |
| ### You set `INSTANCE_ID` yourself, and it names the query collection | ||
|
|
||
| The extension derived its instance id at install and used it for the query | ||
| collection (`_<instance id>/index/queries`), the index metadata document | ||
| (`_<instance id>/index`) and its task queues. Here `INSTANCE_ID` is a setting you | ||
| collection (`_<instance id>/index/queries`), the index metadata and backfill | ||
| progress document (`_<instance id>/index`, with its `enqueues` subcollection) | ||
| and its task queues. Here `INSTANCE_ID` is a setting you | ||
| provide, and it must match this instance's key in the `instances` map in | ||
| `firebase.json`. To keep serving the query documents your clients already write | ||
| to, set it to your installed instance's id. The four task queue names can also be | ||
|
|
@@ -198,35 +199,40 @@ whatever `EMBEDDING_PROVIDER` is set to. If either does not exist, `firebase | |
| deploy` prompts you for a value and fails outright when running | ||
| non-interactively (CI). Create the one you do not need with a placeholder value. | ||
|
|
||
| ### `UPDATE_ON_CONFIGURE` now re-embeds on every deploy | ||
|
|
||
| This setting was declared by the extension but never read. Reconfiguring an | ||
| installed instance re-embedded documents only when the provider, the vector | ||
| dimension or the input/output field names had actually changed, which the | ||
| extension tracked in its index metadata document. | ||
|
|
||
| The kit keeps no such metadata and does no comparison. `UPDATE_ON_CONFIGURE: true` | ||
| enqueues a full re-embed of every document that already has an embedding after | ||
| *every* `firebase deploy`, whether anything relevant changed or not, and | ||
| `DO_BACKFILL: true` embeds the whole collection after the first deploy. On a large | ||
| collection that is a large Vertex AI or OpenAI bill per deploy. Set | ||
| `UPDATE_ON_CONFIGURE: false` and re-embed deliberately when you change providers. | ||
|
|
||
| ### Backfill is one task per document, and reads the collection in one go | ||
|
|
||
| The extension chunked the collection into batches sized to the provider (16 | ||
| documents per OpenAI call), embedded each batch in a single API call, and tracked | ||
| progress in its metadata document. The kit reads the entire collection with one | ||
| `get()` and enqueues one Cloud Task per document, each of which embeds one | ||
| document with one API call. | ||
|
|
||
| Two consequences. A collection large enough that a single `get()` does not fit in | ||
| the trigger's 512 MiB will fail the backfill outright, and there is no | ||
| resume-from-progress. Backfilling *n* documents now costs *n* task invocations and | ||
| *n* embedding calls rather than *n*/batch size. | ||
|
|
||
| There is also no install-time progress reporting, since there is no extension | ||
| install UI to report into. Watch the function logs instead. | ||
| ### `UPDATE_ON_CONFIGURE` is read, and the backfill gate is stricter than the extension's | ||
|
|
||
| This setting was declared by the extension but never read: its update pass was | ||
| gated on `DO_BACKFILL` instead. The kit reads `UPDATE_ON_CONFIGURE`, so the two | ||
| passes are controlled independently — `DO_BACKFILL` after the first deploy, | ||
| `UPDATE_ON_CONFIGURE` after every redeploy. | ||
|
|
||
| Both passes are then gated on the index metadata document at | ||
| `_<instance id>/index`, as the extension's were: a pass runs only when the | ||
| embedding provider, the vector dimension or the input/output field names differ | ||
| from what the last pass recorded there. Redeploying without changing any of them | ||
| enqueues nothing and costs nothing. | ||
|
|
||
| The two passes share that document as their task thread, so only one of them | ||
| runs per deploy: with both settings on, the backfill pass runs, which covers | ||
| every document the update pass would have (the update pass is the same | ||
| eligibility rule plus "and already has an embedding"). | ||
|
|
||
| The extension's gate did not survive its own first pass, because the progress | ||
| counters it wrote to the same document replaced the recorded configuration. The | ||
| kit merges instead, so the comparison fields persist and the gate holds on every | ||
| later deploy. To force a full re-embed without changing any setting, delete the | ||
| `_<instance id>/index` document; its `queries` subcollection is untouched, so | ||
|
Comment on lines
+219
to
+224
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section names two deviations (the merge, and the one-pass rule), but reading
All of these look justified to me under "matching would reproduce a bug", and the PR body already argues most of them. Could you add each to this list with one line on what the extension did? The |
||
| the query documents your clients write to survive. | ||
|
|
||
| ### There is no install-time progress reporting | ||
|
|
||
| The extension reported backfill progress and failures through the extension | ||
| install UI (`setProcessingState`). There is no such surface for a kit, so | ||
| progress is visible in the function logs and in the progress fields on | ||
| `_<instance id>/index` (`backfillJobsTotal`, `backfillJobsProcessed`, | ||
| `backfillJobsSkipped`, `backfillJobsFailed`, `backfillStatus`) instead. One | ||
| document per chunk is written under `_<instance id>/index/enqueues`, as the | ||
| extension did, each carrying its chunk of document ids and its own status. | ||
|
|
||
| ### The `status` field on your documents is a different shape | ||
|
|
||
|
|
@@ -243,10 +249,12 @@ status: { state: "COMPLETED" } | |
| status: { state: "ERROR", message: "<error message>" } | ||
| ``` | ||
|
|
||
| The states themselves are narrower too: `PROCESSING` and `BACKFILLED` are no | ||
| longer written, only `COMPLETED` and `ERROR`. Anything reading | ||
| `status.<instance id>.state`, or a security rule or index keyed to it, needs | ||
| updating. The field name is still `STATUS_FIELD_NAME`, defaulting to `status`. | ||
| The states themselves are narrower too: `PROCESSING` is no longer written. The | ||
| write triggers write `COMPLETED` or `ERROR`, and the backfill and update passes | ||
| write `BACKFILLED` or `FAILED_BACKFILL` alongside a `completeTime`, as the | ||
| extension did. Anything reading `status.<instance id>.state`, or a security rule | ||
| or index keyed to it, needs updating. The field name is still | ||
| `STATUS_FIELD_NAME`, defaulting to `status`. | ||
|
|
||
| Query documents carry the flat shape too, with a `request` record alongside the | ||
| state: | ||
|
|
@@ -316,6 +324,13 @@ for; the Firebase CLI grants these for you. | |
| `onSuccess`, `onError` and `onCompletion` under | ||
| `firebase.extensions.firestore-vector-search.v1.*` but never published any of | ||
| them, and the kit publishes none either. `EVENTARC_CHANNEL` is not read. | ||
| - The backfill and update passes still enumerate the collection by document | ||
| reference, chunk it into 50 document ids per Cloud Task, run one task at a | ||
| time, and embed each chunk in provider-sized batches with a single API call per | ||
| batch (16 documents per OpenAI call). A document whose input is not a string is | ||
| skipped, as is one whose status is already set to anything other than | ||
| `BACKFILLED`. A failed batch marks its documents `FAILED_BACKFILL` and the task | ||
| still succeeds. | ||
| - The indexed collection is still `COLLECTION_NAME` (default `products`), the | ||
| input, output and status fields still default to `input`, `embedding` and | ||
| `status`, and embeddings are still written as native Firestore vectors. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this sentence doesn't match what the code does.
initVectorSearchruns on bothafterFirstDeployandafterRedeploy(src/index.ts:118-123), andhandleInitreturns after enqueuing the backfill wheneverdoBackfillis on (src/handlers.ts:207-212), so withDO_BACKFILL=trueit is the backfill trigger that gets enqueued on every redeploy too, andUPDATE_ON_CONFIGUREnever gets a look in. Lines 301-302 already say "after your first deploy and after every redeploy", so the two paragraphs contradict each other.That also makes the one-pass rule a deviation worth naming. The extension maps install to
backfillTriggerand configure toupdateTrigger(extension.yaml:400-406), and the update pass only touches documents that already have an embedding, so a reconfigure that changedINPUT_FIELD_NAMEleft never-embedded documents alone. Here it backfills them as well. Probably the right call given the shared thread, but it changes which documents get written on a redeploy, so it belongs in the Differences list rather than only in thehandleInitcomment.One more thing this section should say, because "redeploying without changing anything enqueues nothing" is only true from the second kit deploy. On a migrated instance the first deploy re-embeds the whole collection: the extension's
_<instance id>/indexdocument holds only the progress counters after any pass that enqueued tasks (itsset()replaced the config fields), so the gate opens, and the pass then treats every extension-written document as unprocessed because it readsstatus.state(src/backfill.ts:470-472) where the extension wrotestatus.<instance id>.state. That is a bigger bill than the extension's reconfigure, which skipped documents the write trigger had markedCOMPLETEDand re-embedded only the ones its backfill had markedBACKFILLED. WithDO_BACKFILL=trueit lands on the first kit deploy, so people should expect it. Worth checking my reading of the status path there, since that is the #3011 shape and I may be behind on where that landed.Could you reword this paragraph to what actually happens (both settings act on every deploy, backfill wins when both are on), and add the install/configure mapping and the first-deploy re-embed to the Differences list?