Skip to content

Commit acce003

Browse files
committed
wip: batch training + interfaces to reuse pre-trained file
1 parent d3cae6f commit acce003

7 files changed

Lines changed: 233 additions & 14 deletions

File tree

index.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ type IndexCopyable interface {
353353
CopyTo(d index.Directory) error
354354
}
355355

356+
type IndexFileCopyable interface {
357+
UpdateFileInBolt(key []byte, value []byte) error
358+
CopyFile(file string, d index.IndexDirectory) error
359+
}
360+
356361
// FileSystemDirectory is the default implementation for the
357362
// index.Directory interface.
358363
type FileSystemDirectory string

index/scorch/persister.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,6 @@ func copyToDirectory(srcPath string, d index.Directory) (int64, error) {
564564
return 0, fmt.Errorf("GetWriter err: %v", err)
565565
}
566566

567-
// skip
568-
if dest == nil {
569-
return 0, nil
570-
}
571-
572567
sourceFileStat, err := os.Stat(srcPath)
573568
if err != nil {
574569
return 0, err
@@ -847,10 +842,34 @@ func zapFileName(epoch uint64) string {
847842
return fmt.Sprintf("%012x.zap", epoch)
848843
}
849844

845+
func (s *Scorch) updateCentroidIndex(bucket *bolt.Bucket) error {
846+
if bucket == nil {
847+
return nil
848+
}
849+
segmentSnapshot, err := s.loadSegment(bucket)
850+
if err != nil {
851+
return err
852+
}
853+
s.rootLock.Lock()
854+
defer s.rootLock.Unlock()
855+
856+
s.centroidIndex = segmentSnapshot
857+
return nil
858+
}
859+
850860
// bolt snapshot code
851861

852862
func (s *Scorch) loadFromBolt() error {
853863
err := s.rootBolt.View(func(tx *bolt.Tx) error {
864+
centroidIndexBucket := tx.Bucket(util.BoltCentroidIndexKey)
865+
if centroidIndexBucket == nil {
866+
return nil
867+
}
868+
err := s.updateCentroidIndex(centroidIndexBucket)
869+
if err != nil {
870+
return err
871+
}
872+
854873
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
855874
if snapshots == nil {
856875
return nil

index/scorch/scorch.go

Lines changed: 162 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
package scorch
1616

1717
import (
18+
"bytes"
1819
"encoding/json"
1920
"fmt"
21+
"io"
2022
"os"
2123
"path/filepath"
2224
"strings"
@@ -513,7 +515,19 @@ func (s *Scorch) Batch(batch *index.Batch) (err error) {
513515
return err
514516
}
515517

518+
func (s *Scorch) getInternal(key []byte) ([]byte, error) {
519+
s.rootLock.RLock()
520+
defer s.rootLock.RUnlock()
521+
if string(key) == "_centroid_index_complete" {
522+
return []byte(fmt.Sprintf("%t", s.centroidIndex != nil)), nil
523+
}
524+
return nil, nil
525+
}
526+
527+
// min 39 per centroid, recommeded 50
528+
// max 256
516529
func (s *Scorch) Train(batch *index.Batch) error {
530+
// is the lock really needed?
517531
s.rootLock.Lock()
518532
defer s.rootLock.Unlock()
519533
if s.centroidIndex != nil {
@@ -523,6 +537,12 @@ func (s *Scorch) Train(batch *index.Batch) error {
523537
var trainData []index.Document
524538
if s.centroidIndex == nil {
525539
for key, doc := range batch.IndexOps {
540+
if doc != nil {
541+
// insert _id field
542+
// no need to track updates/deletes over here since
543+
// the API is singleton
544+
doc.AddIDField()
545+
}
526546
if strings.HasPrefix(key, index.TrainDataPrefix) {
527547
trainData = append(trainData, doc)
528548
}
@@ -537,11 +557,16 @@ func (s *Scorch) Train(batch *index.Batch) error {
537557
//
538558
// note: this might index text data too, how to handle this? s.segmentConfig?
539559
// todo: updates/deletes -> data drift detection
540-
seg, _, err := s.segPlugin.NewEx(trainData, s.segmentConfig)
560+
s.segmentConfig["training"] = true
561+
seg, n, err := s.segPlugin.NewEx(trainData, s.segmentConfig)
541562
if err != nil {
542563
return err
543564
}
544-
filename := "centroid_index.zap"
565+
// reset the training flag once completed
566+
s.segmentConfig["training"] = false
567+
// not suffixing with .zap since the current garbage collection is tailored to .zap ext files
568+
// we don't want to gc this file ever.
569+
filename := "centroid_index"
545570
path := filepath.Join(s.path, filename)
546571

547572
switch seg := seg.(type) {
@@ -562,7 +587,56 @@ func (s *Scorch) Train(batch *index.Batch) error {
562587
s.centroidIndex = &SegmentSnapshot{
563588
segment: persistedSegment,
564589
}
565-
s.segmentConfig["getCentroidIndexCallback"] = s.getCentroidIndex
590+
591+
fmt.Println("number of bytes written to centroid index", n)
592+
// s.segmentConfig["getCentroidIndexCallback"] = s.getCentroidIndex
593+
// updateBolt(tx, cetntroid)
594+
// filename := "centroid_index"
595+
// path := filepath.Join(s.path, filename)
596+
// f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
597+
// if err != nil {
598+
// return err
599+
// }
600+
601+
// bufw := bufio.NewWriter(f)
602+
// _, err = bufw.Write([]byte(strings.Join([]string{"centroid_index1", path}, " ")))
603+
// if err != nil {
604+
// return err
605+
// }
606+
// err = bufw.Flush()
607+
// if err != nil {
608+
// return err
609+
// }
610+
// err = f.Sync()
611+
// if err != nil {
612+
// return err
613+
// }
614+
// err = f.Close()
615+
// if err != nil {
616+
// return err
617+
// }
618+
619+
tx, err := s.rootBolt.Begin(true)
620+
if err != nil {
621+
return err
622+
}
623+
defer tx.Rollback()
624+
625+
snapshotsBucket, err := tx.CreateBucketIfNotExists(util.BoltSnapshotsBucket)
626+
if err != nil {
627+
return err
628+
}
629+
630+
err = snapshotsBucket.Put(util.BoltCentroidIndexKey, []byte(path))
631+
if err != nil {
632+
return err
633+
}
634+
635+
err = tx.Commit()
636+
if err != nil {
637+
return err
638+
}
639+
566640
return nil
567641
}
568642

@@ -1018,6 +1092,91 @@ func (s *Scorch) CopyReader() index.CopyReader {
10181092
return rv
10191093
}
10201094

1095+
func (s *Scorch) updateCentroidIndexInBolt(tx *bolt.Tx) error {
1096+
centroidIndexBucket, err := tx.CreateBucketIfNotExists(util.BoltCentroidIndexKey)
1097+
if err != nil {
1098+
return err
1099+
}
1100+
1101+
err = centroidIndexBucket.Put(util.BoltPathKey, []byte("centroid_index.zap"))
1102+
if err != nil {
1103+
return err
1104+
}
1105+
1106+
return nil
1107+
}
1108+
1109+
func (s *Scorch) UpdateFileInBolt(key []byte, value []byte) error {
1110+
tx, err := s.rootBolt.Begin(true)
1111+
if err != nil {
1112+
return err
1113+
}
1114+
defer tx.Rollback()
1115+
1116+
snapshotsBucket, err := tx.CreateBucketIfNotExists(util.BoltSnapshotsBucket)
1117+
if err != nil {
1118+
return err
1119+
}
1120+
1121+
// currently this is specific to centroid index file update
1122+
if bytes.Equal(key, util.BoltCentroidIndexKey) {
1123+
// guard against duplicate updates
1124+
existingValue := snapshotsBucket.Get(key)
1125+
if existingValue != nil {
1126+
return fmt.Errorf("key already exists")
1127+
}
1128+
1129+
err = snapshotsBucket.Put(key, value)
1130+
if err != nil {
1131+
return err
1132+
}
1133+
}
1134+
1135+
err = tx.Commit()
1136+
if err != nil {
1137+
return err
1138+
}
1139+
1140+
err = s.rootBolt.Sync()
1141+
if err != nil {
1142+
return err
1143+
}
1144+
1145+
return nil
1146+
}
1147+
1148+
// CopyFile copies a specific file to a destination directory which has an access to a bleve index
1149+
// doing a io.Copy() isn't enough because the file needs to be tracked in bolt file as well
1150+
func (s *Scorch) CopyFile(file string, d index.IndexDirectory) error {
1151+
s.rootLock.Lock()
1152+
defer s.rootLock.Unlock()
1153+
1154+
// this code is currently specific to centroid index file but is future proofed for other files
1155+
// to be updated in the dest's bolt
1156+
if strings.HasSuffix(file, "centroid_index") {
1157+
// centroid index file - this is outside the snapshots domain so the bolt update is different
1158+
err := d.UpdateFileInBolt(util.BoltCentroidIndexKey, []byte(file))
1159+
if err != nil {
1160+
return err
1161+
}
1162+
}
1163+
1164+
dest, err := d.GetWriter(filepath.Join("store", file))
1165+
if err != nil {
1166+
return err
1167+
}
1168+
1169+
source, err := os.Open(filepath.Join(s.path, file))
1170+
if err != nil {
1171+
return err
1172+
}
1173+
1174+
defer source.Close()
1175+
defer dest.Close()
1176+
_, err = io.Copy(dest, source)
1177+
return err
1178+
}
1179+
10211180
// external API to fire a scorch event (EventKindIndexStart) externally from bleve
10221181
func (s *Scorch) FireIndexEvent() {
10231182
s.fireEvent(EventKindIndexStart, 0)

index/scorch/snapshot_index.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ func (is *IndexSnapshot) Fields() ([]string, error) {
469469
}
470470

471471
func (is *IndexSnapshot) GetInternal(key []byte) ([]byte, error) {
472+
_, ok := is.internal[string(key)]
473+
if !ok {
474+
return is.parent.getInternal(key)
475+
}
472476
return is.internal[string(key)], nil
473477
}
474478

index_alias_impl.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,13 @@ func (i *indexAliasImpl) Train(batch *Batch) error {
110110
if !i.open {
111111
return ErrorIndexClosed
112112
}
113-
114113
err := i.isAliasToSingleIndex()
115114
if err != nil {
116115
return err
117116
}
118117

119-
if vi, ok := i.indexes[0].(VectorIndex); ok {
120-
return vi.Train(batch)
118+
if vi, ok := i.indexes[0].(index.VectorIndex); ok {
119+
return vi.Train(batch.internal)
121120
}
122121
return fmt.Errorf("not a vector index")
123122
}

index_impl.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,8 @@ func (i *indexImpl) Train(batch *Batch) error {
377377
return ErrorIndexClosed
378378
}
379379

380-
if vi, ok := i.i.(VectorIndex); ok {
381-
return vi.Train(batch)
380+
if vi, ok := i.i.(index.VectorIndex); ok {
381+
return vi.Train(batch.internal)
382382
}
383383
return fmt.Errorf("not a vector index")
384384
}
@@ -1376,6 +1376,38 @@ func (m *searchHitSorter) Less(i, j int) bool {
13761376
return c < 0
13771377
}
13781378

1379+
func (i *indexImpl) CopyFile(file string, d index.IndexDirectory) (err error) {
1380+
i.mutex.RLock()
1381+
defer i.mutex.RUnlock()
1382+
1383+
if !i.open {
1384+
return ErrorIndexClosed
1385+
}
1386+
1387+
copyIndex, ok := i.i.(index.IndexFileCopyable)
1388+
if !ok {
1389+
return fmt.Errorf("index implementation does not support copy reader")
1390+
}
1391+
1392+
return copyIndex.CopyFile(file, d)
1393+
}
1394+
1395+
func (i *indexImpl) UpdateFileInBolt(key []byte, value []byte) error {
1396+
i.mutex.RLock()
1397+
defer i.mutex.RUnlock()
1398+
1399+
if !i.open {
1400+
return ErrorIndexClosed
1401+
}
1402+
1403+
copyIndex, ok := i.i.(index.IndexFileCopyable)
1404+
if !ok {
1405+
return fmt.Errorf("index implementation does not support file copy")
1406+
}
1407+
1408+
return copyIndex.UpdateFileInBolt(key, value)
1409+
}
1410+
13791411
// CopyTo (index.Directory, filter)
13801412
func (i *indexImpl) CopyTo(d index.Directory) (err error) {
13811413
i.mutex.RLock()
@@ -1405,7 +1437,7 @@ func (i *indexImpl) CopyTo(d index.Directory) (err error) {
14051437

14061438
err = copyReader.CopyTo(d)
14071439
if err != nil {
1408-
return fmt.Errorf("error copying index metadata: %v", err)
1440+
return fmt.Errorf("error copying index data: %v", err)
14091441
}
14101442

14111443
// copy the metadata

util/keys.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package util
1717
var (
1818
// Bolt keys
1919
BoltSnapshotsBucket = []byte{'s'}
20+
BoltCentroidIndexKey = []byte{'c'}
2021
BoltPathKey = []byte{'p'}
2122
BoltDeletedKey = []byte{'d'}
2223
BoltInternalKey = []byte{'i'}

0 commit comments

Comments
 (0)