@@ -123,11 +123,12 @@ const vectorProgressStride = 2000
123123// the schema-qualified halfvec type. The type must be qualified because the
124124// connection's search_path is the target schema only, while pgvector's types
125125// live in whichever schema first installed the extension.
126- // machineRecorded reports whether this machine's push record already existed
127- // for the generation before this push touched it: recreating the vector
128- // tables restarts the id sequence, so a reused id can satisfy the memo
129- // comparison while the recreated generation is empty, and the machine record
130- // — wiped in the same reset — is the witness that survives id reuse.
126+ // machineRecorded reports whether this pusher's scoped witness record already
127+ // existed for the generation before this push touched it: recreating the
128+ // vector tables restarts the id sequence, so a reused id can satisfy the memo
129+ // comparison while the recreated generation is empty, and the witness record
130+ // — keyed by local push marker plus sync/filter scope and wiped in the same
131+ // reset — is what survives id reuse safely.
131132type vectorGeneration struct {
132133 id int64
133134 halfvecType string
@@ -270,26 +271,29 @@ func (s *Sync) pushVectors(
270271 res .Skipped , res .SkippedReason = true , unavailable
271272 return res , nil
272273 }
274+ witnessKey , err := s .vectorGenerationWitnessKey ()
275+ if err != nil {
276+ return res , err
277+ }
273278 // A scoped push writes only its changed sessions' chunks, which is safe
274279 // only within the exact generation instance this process last reconciled
275280 // generation-wide. Two signals identify that instance. The PG generation
276281 // id catches every recreation that keeps the tables: row deletion never
277282 // resets the sequence, so a re-embed (new fingerprint), reset, or admin
278283 // drop yields a new id, whoever recreates it. Recreating the tables
279284 // themselves restarts the sequence, so a reused id can match a stale
280- // memo; this machine's push record, wiped in the same reset and written
281- // only after a clean generation-wide reconciliation, is the witness for
282- // that case — a scoped push always follows a generation-wide push in the
283- // same process, which recorded it. Either signal failing means the prior
284- // reconciliation no longer covers this generation and scoping would
285- // leave search reading an incomplete one until the interval floor.
285+ // memo; this pusher's scoped witness record, wiped in the same reset and
286+ // written only after a clean generation-wide reconciliation, is the
287+ // witness for that case. Either signal failing means the prior
288+ // reconciliation no longer covers this generation and scoping would leave
289+ // search reading an incomplete one until the interval floor.
286290 // Promote to a generation-wide read. A zero memo means no reconciliation
287291 // to trust yet, so the reconcile bit already forces this push
288292 // generation-wide and the id check must not fire.
289293 requestedScope := scope
290294 var resolved vectorGeneration
291295 if scope != nil {
292- initialProbe , found , err := s .lookupVectorGeneration (ctx , gen .Fingerprint )
296+ initialProbe , found , err := s .lookupVectorGeneration (ctx , gen .Fingerprint , witnessKey )
293297 if err != nil {
294298 return res , err
295299 }
@@ -298,8 +302,8 @@ func (s *Sync) pushVectors(
298302 gen .Fingerprint )
299303 scope = nil
300304 } else if ! initialProbe .machineRecorded {
301- log .Printf ("vector push: no prior push record for machine %q against generation %d; promoting scoped push to generation-wide reconciliation" ,
302- s . machine , initialProbe .id )
305+ log .Printf ("vector push: no prior scoped witness against generation %d; promoting scoped push to generation-wide reconciliation" ,
306+ initialProbe .id )
303307 scope = nil
304308 } else if lastReconciledGeneration != 0 &&
305309 initialProbe .id != lastReconciledGeneration {
@@ -312,7 +316,7 @@ func (s *Sync) pushVectors(
312316 s .afterVectorGenerationLookup = nil
313317 hook ()
314318 }
315- currentProbe , found , err := s .lookupVectorGeneration (ctx , gen .Fingerprint )
319+ currentProbe , found , err := s .lookupVectorGeneration (ctx , gen .Fingerprint , witnessKey )
316320 if err != nil {
317321 return res , err
318322 }
@@ -321,8 +325,8 @@ func (s *Sync) pushVectors(
321325 gen .Fingerprint )
322326 scope = nil
323327 } else if ! currentProbe .machineRecorded {
324- log .Printf ("vector push: no prior push record for machine %q against generation %d before scoped reconciliation; promoting scoped push to generation-wide reconciliation" ,
325- s . machine , currentProbe .id )
328+ log .Printf ("vector push: no prior scoped witness against generation %d before scoped reconciliation; promoting scoped push to generation-wide reconciliation" ,
329+ currentProbe .id )
326330 scope = nil
327331 } else if currentProbe .id != initialProbe .id {
328332 log .Printf ("vector push: active generation id changed from %d to %d before scoped reconciliation; promoting scoped push to generation-wide reconciliation" ,
@@ -358,7 +362,7 @@ func (s *Sync) pushVectors(
358362 gen = export .Generation ()
359363 }
360364 if scope == nil {
361- resolved , err = s .resolveVectorGeneration (ctx , gen )
365+ resolved , err = s .resolveVectorGeneration (ctx , gen , witnessKey )
362366 if err != nil {
363367 if s .skipVectorsOnPrivilegeError (err , & res ) {
364368 return res , nil
@@ -413,7 +417,7 @@ func (s *Sync) pushVectors(
413417 ctx , full , nil , lastReconciledGeneration , failedSessions , onProgress ,
414418 )
415419 }
416- finalProbe , found , err := s .lookupVectorGeneration (ctx , gen .Fingerprint )
420+ finalProbe , found , err := s .lookupVectorGeneration (ctx , gen .Fingerprint , witnessKey )
417421 if err != nil {
418422 return res , err
419423 }
@@ -425,8 +429,8 @@ func (s *Sync) pushVectors(
425429 }
426430 if ! finalProbe .machineRecorded {
427431 return retryGenerationWide (
428- "vector push: no prior push record for machine %q against generation %d after scoped reconciliation; retrying generation-wide" ,
429- s . machine , finalProbe .id ,
432+ "vector push: no prior scoped witness against generation %d after scoped reconciliation; retrying generation-wide" ,
433+ finalProbe .id ,
430434 )
431435 }
432436 if finalProbe .id != resolved .id {
@@ -437,7 +441,7 @@ func (s *Sync) pushVectors(
437441 }
438442 }
439443 if scope == nil && res .SessionsDeferred == 0 {
440- if err := s .recordVectorGenerationMachine (ctx , resolved .id ); err != nil {
444+ if err := s .recordVectorGenerationMachine (ctx , resolved .id , witnessKey ); err != nil {
441445 return res , err
442446 }
443447 }
@@ -448,7 +452,7 @@ func (s *Sync) pushVectors(
448452}
449453
450454func (s * Sync ) lookupVectorGeneration (
451- ctx context.Context , fingerprint string ,
455+ ctx context.Context , fingerprint , witnessKey string ,
452456) (vectorGeneration , bool , error ) {
453457 genID , _ , ok , err := LookupVectorGeneration (ctx , s .pg , fingerprint )
454458 if err != nil {
@@ -466,7 +470,7 @@ func (s *Sync) lookupVectorGeneration(
466470SELECT EXISTS (
467471 SELECT 1 FROM vector_generation_machines
468472 WHERE generation_id = $1 AND machine = $2)` ,
469- genID , s . machine ).Scan (& machineRecorded ); err != nil {
473+ genID , witnessKey ).Scan (& machineRecorded ); err != nil {
470474 return vectorGeneration {}, false , fmt .Errorf (
471475 "reading vector push machine record: %w" , err ,
472476 )
@@ -801,11 +805,11 @@ func vectorOutOfScopeQuery(
801805// against it. Its id is the generation
802806// instance's identity, which a scoped push compares against the last one it
803807// reconciled generation-wide to decide whether to promote; whether this
804- // machine's push record predated this call is captured first, because that
805- // record is the incarnation witness the promotion check falls back on when a
806- // recreated id sequence hands the new generation the memoized id.
808+ // scoped witness predated this call is captured first, because that record is
809+ // the incarnation witness the promotion check falls back on when a recreated
810+ // id sequence hands the new generation the memoized id.
807811func (s * Sync ) resolveVectorGeneration (
808- ctx context.Context , gen VectorGenerationInfo ,
812+ ctx context.Context , gen VectorGenerationInfo , witnessKey string ,
809813) (vectorGeneration , error ) {
810814 genID , err := ensureVectorGeneration (
811815 ctx , s .pg , gen .Fingerprint , gen .Model , gen .Dimension ,
@@ -825,7 +829,7 @@ func (s *Sync) resolveVectorGeneration(
825829SELECT EXISTS (
826830 SELECT 1 FROM vector_generation_machines
827831 WHERE generation_id = $1 AND machine = $2)` ,
828- genID , s . machine ).Scan (& machineRecorded ); err != nil {
832+ genID , witnessKey ).Scan (& machineRecorded ); err != nil {
829833 return vectorGeneration {}, fmt .Errorf ("reading vector push machine record: %w" , err )
830834 }
831835 return vectorGeneration {
@@ -835,14 +839,22 @@ SELECT EXISTS (
835839 }, nil
836840}
837841
842+ func (s * Sync ) vectorGenerationWitnessKey () (string , error ) {
843+ markerID , err := s .pushMarkerID ()
844+ if err != nil {
845+ return "" , err
846+ }
847+ return s .machine + "|" + s .pushMarkerMetadataKey (pushMarkerKeyPrefix , markerID ), nil
848+ }
849+
838850func (s * Sync ) recordVectorGenerationMachine (
839- ctx context.Context , genID int64 ,
851+ ctx context.Context , genID int64 , witnessKey string ,
840852) error {
841853 if _ , err := s .pg .ExecContext (ctx , `
842854INSERT INTO vector_generation_machines (generation_id, machine, last_push_at)
843855VALUES ($1, $2, now())
844856ON CONFLICT (generation_id, machine) DO UPDATE SET last_push_at = now()` ,
845- genID , s . machine ); err != nil {
857+ genID , witnessKey ); err != nil {
846858 return fmt .Errorf ("recording vector push machine: %w" , err )
847859 }
848860 return nil
0 commit comments