Skip to content

Commit 80a54a1

Browse files
feat: ensure RediSearch index exists on startup
On startup, omni-pitcher now checks if the RediSearch index exists via FT.INFO and creates it with FT.CREATE ON JSON if missing. This prevents "no such index" errors on fresh deployments or Redis restarts. Skips index creation when REDIS_SEARCH_INDEX is not configured. Closes #95 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 85531a3 commit 80a54a1

3 files changed

Lines changed: 85 additions & 0 deletions

File tree

internal/pitcher/index.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package pitcher
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"strings"
8+
9+
"github.com/redis/go-redis/v9"
10+
)
11+
12+
// EnsureIndex checks whether the RediSearch index exists and creates it if missing.
13+
// This should be called once at startup before any messages are pitched.
14+
func (p *RedisPitcher) EnsureIndex(ctx context.Context) error {
15+
if p.Config.Index == "" {
16+
slog.Debug("redisearch index not configured, skipping ensure")
17+
return nil
18+
}
19+
20+
client := redis.NewClient(&redis.Options{
21+
Addr: p.Config.Addr + ":" + p.Config.Port,
22+
Password: p.Config.Password,
23+
})
24+
defer func() { _ = client.Close() }()
25+
26+
// Check if index already exists
27+
_, err := client.Do(ctx, "FT.INFO", p.Config.Index).Result()
28+
if err == nil {
29+
slog.Info("redisearch index already exists", "index", p.Config.Index)
30+
return nil
31+
}
32+
33+
if !strings.Contains(err.Error(), "no such index") {
34+
return fmt.Errorf("failed to check redisearch index: %w", err)
35+
}
36+
37+
slog.Info("redisearch index not found, creating", "index", p.Config.Index)
38+
39+
args := []any{
40+
"FT.CREATE", p.Config.Index,
41+
"ON", "JSON",
42+
"SCHEMA",
43+
"$.severity", "AS", "severity", "TAG",
44+
"$.system", "AS", "system", "TAG",
45+
"$.timestamp", "AS", "timestamp", "TEXT",
46+
"$.title", "AS", "title", "TEXT",
47+
"$.message", "AS", "message", "TEXT",
48+
"$.author", "AS", "author", "TAG",
49+
"$.tags", "AS", "tags", "TAG",
50+
}
51+
52+
if err := client.Do(ctx, args...).Err(); err != nil {
53+
return fmt.Errorf("failed to create redisearch index: %w", err)
54+
}
55+
56+
slog.Info("redisearch index created", "index", p.Config.Index)
57+
return nil
58+
}

internal/pitcher/index_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pitcher
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
homerun "github.com/stuttgart-things/homerun-library/v2"
8+
)
9+
10+
func TestEnsureIndexSkipsWhenEmpty(t *testing.T) {
11+
rp := &RedisPitcher{
12+
Config: homerun.RedisConfig{
13+
Index: "",
14+
},
15+
}
16+
17+
// Should return nil immediately when index is not configured
18+
err := rp.EnsureIndex(context.Background())
19+
if err != nil {
20+
t.Errorf("EnsureIndex() with empty index should return nil, got: %v", err)
21+
}
22+
}

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ func main() {
5252
}
5353
cancel()
5454

55+
// Ensure RediSearch index exists before accepting requests
56+
if err := rp.EnsureIndex(context.Background()); err != nil {
57+
slog.Warn("failed to ensure redisearch index", "index", redisConfig.Index, "error", err)
58+
}
59+
5560
p = rp
5661
slog.Info("pitcher mode: redis", "addr", redisConfig.Addr, "port", redisConfig.Port, "stream", redisConfig.Stream, "searchIndex", redisConfig.Index)
5762
}

0 commit comments

Comments
 (0)