Search initially seems simple: type a query, get matching content from the database. But once I started trying to make BrainZen—my second-brain app—actually understand what I'm looking for by meaning
learningwithyahya.hashnode.dev5 min read
The asynchronous embedding lifecycle needs a rule for completion order as well as content edits. If a note changes twice while two embedding jobs are in flight, the older job may finish last and replace the newer vector even though every job succeeded.
I would attach the note revision to each job and accept its vector only if that revision still matches the current content. A test can deliberately finish the newer job first, then the older one, and assert that search remains tied to the latest revision. That turns stale-vector detection into a property of writes rather than a periodic cleanup hope.
Mihai_LeanZero
Atlassian consultant and Forge developer at LeanZero. We do Data Center to Cloud migrations and build Forge apps, and I write up what I learn at leanzero.net.
The embedding lifecycle point is the one people skip and it bites twice. What you hit is staleness on edit, but the other failure shows up once re-embedding runs async: two edits close together can produce two jobs that finish out of order, so a stale embedding can overwrite the fresh one unless each job is tagged with the note's version or last-modified time and anything arriving out of order gets dropped. On the RRF side, where the metadata or permission filter sits matters just as much - filtering after RRF has already picked the top-K means you can end up serving fewer than K results whenever the top-ranked items get filtered out, since fusion already discarded everything outside that window. Filtering each retrieval path before fusion, or over-fetching before the final cut, avoids that particular way of quietly returning less than you promised.