an app.bsky.* indexer

create firehoseCursor model, drop cursorRecord model

Changed files
+45 -69
cmd
+6 -65
cmd/monarch/cursors.go
···
import (
"context"
-
"fmt"
"log/slog"
-
"strconv"
"sync"
"time"
"gorm.io/gorm"
)
-
type cursorRecord struct {
-
ID uint `gorm:"primaryKey"`
-
Key string
-
Val string
-
}
-
type CursorService struct {
store *gorm.DB
firehoseLk sync.Mutex
firehoseSeq int64
-
-
reposLk sync.Mutex
-
reposSeq string
}
func NewCursorService(store *gorm.DB) *CursorService {
-
store.AutoMigrate(&cursorRecord{})
-
-
var rec cursorRecord
-
store.First(&rec, 1)
-
if rec.ID == 0 {
-
store.Create(&cursorRecord{ID: 1, Key: "firehose", Val: ""})
-
}
-
-
store.First(&rec, 2)
-
if rec.ID == 0 {
-
store.Create(&cursorRecord{ID: 2, Key: "repos", Val: ""})
-
}
+
store.AutoMigrate(&firehoseCursor{})
return &CursorService{
store: store,
}
}
-
func (cs *CursorService) Get(key string) (string, error) {
-
var rec cursorRecord
-
if err := cs.store.Where("key = ?", key).First(&rec).Error; err != nil {
-
return "", fmt.Errorf("error fetching cursor record: %w", err)
-
}
-
return rec.Val, nil
-
}
-
-
func (cs *CursorService) SetFirehoseCursor(seq int64) {
-
cs.firehoseLk.Lock()
-
cs.firehoseSeq = seq
-
cs.firehoseLk.Unlock()
-
}
-
-
func (cs *CursorService) SetReposCursor(value string) {
-
cs.reposLk.Lock()
-
cs.reposSeq = value
-
cs.reposLk.Unlock()
-
}
-
-
func (cs *CursorService) Flush() error {
-
cs.firehoseLk.Lock()
-
fcursor := strconv.Itoa(int(cs.firehoseSeq))
-
if err := cs.store.Model(&cursorRecord{}).Where("key = ?", "firehose").Update("val", fcursor).Error; err != nil {
-
return fmt.Errorf("error updating cursor record: %w", err)
-
}
-
cs.firehoseLk.Unlock()
-
-
cs.reposLk.Lock()
-
if err := cs.store.Model(&cursorRecord{}).Where("key = ?", "repos").Update("val", cs.reposSeq).Error; err != nil {
-
return fmt.Errorf("error updating cursor record: %w", err)
-
}
-
cs.reposLk.Unlock()
-
-
return nil
-
}
-
-
func (cs *CursorService) CheckpointCursors(ctx context.Context) {
+
func (cs *CursorService) Checkpoint(ctx context.Context) {
t := time.NewTicker(time.Second * 5)
defer t.Stop()
for {
select {
case <-ctx.Done():
-
slog.Info("stopping cursor checkpointer")
+
slog.Info("stopping cursor checkpointer", "err", ctx.Err())
return
case <-t.C:
}
-
slog.Info("flushing cursors", "firehose", cs.firehoseSeq, "repos", cs.reposSeq)
-
if err := cs.Flush(); err != nil {
-
slog.Error("error flushing cursors", "err", err)
+
slog.Info("persisting firehose cursor", "seq", cs.firehoseSeq)
+
if err := cs.PersistFirehoseCursor(); err != nil {
+
slog.Error("error persisting firehose cursor", "err", err)
return
}
}
+38 -3
cmd/monarch/firehose.go
···
func NewFirehoseConnection(ctx context.Context, cctx *cli.Context, cursorSvc *CursorService) (*websocket.Conn, error) {
url := fmt.Sprintf("wss://%s/xrpc/com.atproto.sync.subscribeRepos", cctx.String("relay-host"))
-
curs, _ := cursorSvc.Get("firehose")
-
if curs != "" {
-
url += "?cursor=" + curs
+
curs, err := cursorSvc.GetFirehoseCursor()
+
if err == nil { // reversed
+
url += fmt.Sprintf("?cursor=%d", curs)
}
conn, _, err := websocket.DefaultDialer.DialContext(ctx, url, http.Header{
···
return conn, nil
}
+
+
type firehoseCursor struct {
+
ID int `gorm:"primaryKey"`
+
Key string
+
Val int64
+
}
+
+
func (cs *CursorService) GetFirehoseCursor() (int64, error) {
+
cs.firehoseLk.Lock()
+
defer cs.firehoseLk.Unlock()
+
+
var fcur firehoseCursor
+
if err := cs.store.Where("key = ?", "firehose").Attrs(firehoseCursor{Val: cs.firehoseSeq}).FirstOrCreate(&fcur).Error; err != nil {
+
return 0, fmt.Errorf("error getting firehose seq from DB: %w", err)
+
}
+
return fcur.Val, nil
+
}
+
+
func (cs *CursorService) SetFirehoseCursor(seq int64) {
+
cs.firehoseLk.Lock()
+
defer cs.firehoseLk.Unlock()
+
+
cs.firehoseSeq = seq
+
}
+
+
func (cs *CursorService) PersistFirehoseCursor() error {
+
cs.firehoseLk.Lock()
+
defer cs.firehoseLk.Unlock()
+
+
var fcur firehoseCursor
+
if err := cs.store.Where("key = ?", "firehose").Assign(firehoseCursor{Val: cs.firehoseSeq}).FirstOrCreate(&fcur).Error; err != nil {
+
return fmt.Errorf("error persisting firehose seq: %w", err)
+
}
+
return nil
+
}
+1 -1
cmd/monarch/main.go
···
slog.Info("starting up")
app.cursor = NewCursorService(app.state)
-
go app.cursor.CheckpointCursors(ctx)
+
go app.cursor.Checkpoint(ctx)
app.handler = NewHandlerService(app.content)