Skip to content

Commit c8fce31

Browse files
JamesReateclaude
andauthored
Fix OOM in search-sync + schedule daily cron (#313)
* schedule sync-device-definitions-search daily, remove deprecated r1 CLI Replaces the r1-search-gsheet-sync cron entry with device-definitions-search-sync (daily 13:00 UTC) and drops the sync-r1-compatibilty subcommand and its registration. The runtime read path for r1_compatibility is untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix pagination in tableland definitions query to stop OOM QueryDefinitionsCustom built its SQL with OFFSET set to the raw pageIndex instead of pageIndex*500, so each page overlapped the previous by 499 rows. The caller in sync-device-definitions-search also broke only when a page returned fewer than 50 rows (vs. the 500-row page size), so for any manufacturer with more than 50 definitions the loop kept advancing by one row while appending a near-duplicate page each iteration. The resulting documents slice grew by roughly N*(N-50)/2 per make and drove the job OOM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor sync-device-definitions-search for per-make flush and testability Splits Execute() into a SearchIndexer interface (with a typesense-backed implementation) and two pure orchestration functions: buildManufacturerDocuments converts a single manufacturer's tableland definitions into search entries, and runSearchSync walks manufacturers and upserts one make at a time. The per-make flush bounds steady-state memory to one make's documents instead of retaining the entire catalog until the end, and switches uploads from per-document Upsert calls to the batched Documents().Import upsert action. Adds unit tests covering the year filter, field population, pagination termination, error propagation, and per-make flush behaviour. Tests use the existing gomock mocks for IdentityAPI and DeviceDefinitionOnChainService plus a generated MockSearchIndexer; no database is required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 69412db commit c8fce31

8 files changed

Lines changed: 438 additions & 366 deletions

File tree

charts/device-definitions-api/values-prod.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,10 @@ tolerations: []
5858
affinity: {}
5959
podDisruptionBudget:
6060
minAvailable: 1
61+
jobs:
62+
- name: device-definitions-search-sync
63+
schedule: 0 13 * * *
64+
args:
65+
- '-c'
66+
- /device-definitions-api sync-device-definitions-search; CODE=$?; echo "daily device definitions typesense search sync completed"; wget -q --post-data "hello=shutdown" http://localhost:4191/shutdown &> /dev/null; exit $CODE;
67+

cmd/device-definitions-api/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ func main() {
5555
subcommands.Register(&decodeVINCmd{logger: &logger, settings: &settings}, "")
5656
subcommands.Register(&syncDeviceDefinitionSearchCmd{logger: logger, settings: settings, sender: sigSender}, "")
5757
subcommands.Register(&deleteDefinition{logger: logger, settings: settings}, "")
58-
subcommands.Register(&syncR1CompatibiltyCmd{logger: logger, settings: settings}, "")
5958
subcommands.Register(&bulkUpdatePowertrain{logger: logger, settings: settings, sender: sigSender}, "")
6059

6160
if len(os.Args) == 1 {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/pkg/errors"
8+
"github.com/typesense/typesense-go/typesense"
9+
"github.com/typesense/typesense-go/typesense/api"
10+
)
11+
12+
//go:generate mockgen -source search_indexer.go -destination search_indexer_mock_test.go -package main
13+
type SearchIndexer interface {
14+
RecreateIndex(ctx context.Context, schema *api.CollectionSchema) error
15+
UpsertDocuments(ctx context.Context, collectionName string, docs []SearchEntryItem) error
16+
}
17+
18+
type typesenseSearchIndexer struct {
19+
client *typesense.Client
20+
}
21+
22+
func NewTypesenseSearchIndexer(client *typesense.Client) SearchIndexer {
23+
return &typesenseSearchIndexer{client: client}
24+
}
25+
26+
func (t *typesenseSearchIndexer) RecreateIndex(ctx context.Context, schema *api.CollectionSchema) error {
27+
if _, err := t.client.Collection(schema.Name).Delete(ctx); err != nil {
28+
// Deletion failure is non-fatal (index may not exist yet) — caller logs.
29+
fmt.Printf("RecreateIndex: delete returned %v (continuing)\n", err)
30+
}
31+
if _, err := t.client.Collections().Create(ctx, schema); err != nil {
32+
return errors.Wrap(err, "failed to create collection")
33+
}
34+
return nil
35+
}
36+
37+
func (t *typesenseSearchIndexer) UpsertDocuments(ctx context.Context, collectionName string, docs []SearchEntryItem) error {
38+
if len(docs) == 0 {
39+
return nil
40+
}
41+
payload := make([]interface{}, 0, len(docs))
42+
for _, d := range docs {
43+
payload = append(payload, d)
44+
}
45+
action := "upsert"
46+
_, err := t.client.Collection(collectionName).Documents().Import(ctx, payload, &api.ImportDocumentsParams{
47+
Action: &action,
48+
})
49+
if err != nil {
50+
return errors.Wrap(err, "failed to import documents")
51+
}
52+
return nil
53+
}

cmd/device-definitions-api/search_indexer_mock_test.go

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)