@@ -11,78 +11,25 @@ import (
1111var errNotTrained = errors .New ("index backend not trained: run train first" )
1212
1313func (m * IMDB ) ExportVectors (ctx context.Context , count int ) ([][]byte , error ) {
14- imgCount , totalVec , err := m .db .GetCount (ctx )
14+ _ , totalVec , err := m .db .GetCount (ctx )
1515 if err != nil {
1616 return nil , err
1717 }
18- if imgCount == 0 || totalVec == 0 {
18+ if totalVec == 0 {
1919 return nil , nil
2020 }
2121 if int64 (count ) >= totalVec {
2222 return m .exportSequential (ctx , count )
2323 }
24-
25- avgPerImg := max (int (totalVec / imgCount ), 1 )
26- imgSample := min (max (count / avgPerImg , 1 ), int (imgCount ))
27-
28- out := make ([][]byte , 0 , count )
29- sqlDB := m .db .SQL ()
30-
31- rows , err := sqlDB .QueryContext (ctx ,
32- `SELECT v.vector FROM vector v
33- WHERE v.id IN (
34- SELECT id FROM image
35- WHERE id IN (SELECT abs(random()) % (SELECT coalesce(max(id),0)+1 FROM image) FROM image LIMIT ?)
36- )
37- ORDER BY v.id ASC` , imgSample * 3 )
38- if err != nil {
39- // Fallback: sequential stride sampling (slow but memory-safe).
40- return m .exportStride (ctx , count , totalVec )
41- }
42- defer rows .Close ()
43-
44- keepRatio := float64 (count ) / float64 (totalVec )
45- step := 1
46- if keepRatio > 0 && keepRatio < 1 {
47- step = int (1.0 / keepRatio )
24+ // totalVec from GetCount is a high-water mark (MAX(total_vector_count)),
25+ // not the live vector count after deletes. Use the actual count to compute
26+ // the sampling stride so we don't under-sample.
27+ actualVec , err := m .db .CountVectors (ctx )
28+ if err != nil || actualVec == 0 {
29+ actualVec = totalVec
4830 }
49- if step < 1 {
50- step = 1
51- }
52-
53- for rows .Next () {
54- var blob []byte
55- if err := rows .Scan (& blob ); err != nil {
56- return nil , err
57- }
58- n := len (blob ) / m .codeSize
59- for i := 0 ; i < n ; i += step {
60- code := make ([]byte , m .codeSize )
61- copy (code , blob [i * m .codeSize :(i + 1 )* m .codeSize ])
62- out = append (out , code )
63- if len (out ) >= count {
64- return out , nil
65- }
66- }
67- }
68- if len (out ) < count {
69- // Random sampling didn't yield enough; fall back to stride.
70- return m .exportStride (ctx , count , totalVec )
71- }
72- return out , rows .Err ()
73- }
74-
75- // exportStride reads vectors in id order with a stride, keeping memory bounded.
76- func (m * IMDB ) exportStride (ctx context.Context , count int , totalVec int64 ) ([][]byte , error ) {
77- keepRatio := float64 (count ) / float64 (totalVec )
78- step := 1
79- if keepRatio > 0 && keepRatio < 1 {
80- step = int (1.0 / keepRatio )
81- }
82- if step < 1 {
83- step = 1
84- }
85- out := make ([][]byte , 0 , count )
31+ keepRatio := float64 (count ) / float64 (actualVec )
32+ var out [][]byte
8633 const batch = 500
8734 var offset int64
8835 for len (out ) < count {
@@ -95,6 +42,13 @@ func (m *IMDB) exportStride(ctx context.Context, count int, totalVec int64) ([][
9542 }
9643 for _ , r := range rows {
9744 n := len (r .Vector ) / m .codeSize
45+ step := 1
46+ if keepRatio > 0 {
47+ step = int (1.0 / keepRatio )
48+ }
49+ if step < 1 {
50+ step = 1
51+ }
9852 for i := 0 ; i < n ; i += step {
9953 code := make ([]byte , m .codeSize )
10054 copy (code , r .Vector [i * m .codeSize :(i + 1 )* m .codeSize ])
0 commit comments