main.go raw
1 // orly-db-badger is a standalone gRPC database server using the Badger backend.
2 package main
3
4 import (
5 "context"
6 "os"
7 "path/filepath"
8 "time"
9
10 "go-simpler.org/env"
11 "next.orly.dev/pkg/lol"
12 "next.orly.dev/pkg/lol/chk"
13 "next.orly.dev/pkg/lol/log"
14
15 "next.orly.dev/pkg/database"
16 "next.orly.dev/pkg/database/server"
17 )
18
19 // Config holds the database server configuration.
20 type Config struct {
21 // Listen is the gRPC server listen address
22 Listen string `env:"ORLY_DB_LISTEN" default:"127.0.0.1:50051" usage:"gRPC server listen address"`
23
24 // DataDir is the database data directory
25 DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory"`
26
27 // LogLevel is the logging level
28 LogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"`
29
30 // Badger configuration
31 BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"1024" usage:"block cache size in MB"`
32 IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"512" usage:"index cache size in MB"`
33 ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level (1-19)"`
34
35 // Query cache configuration
36 QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"256" usage:"query cache size in MB"`
37 QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"`
38 QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"`
39
40 // Serial cache configuration
41 SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"`
42 SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"`
43
44 // gRPC server configuration
45 StreamBatchSize int `env:"ORLY_DB_STREAM_BATCH_SIZE" default:"100" usage:"events per stream batch"`
46 }
47
48 func main() {
49 cfg := loadConfig()
50
51 // Set log level
52 lol.SetLogLevel(cfg.LogLevel)
53 log.I.F("orly-db-badger starting with log level: %s", cfg.LogLevel)
54
55 ctx, cancel := context.WithCancel(context.Background())
56 defer cancel()
57
58 // Create database configuration
59 dbCfg := &database.DatabaseConfig{
60 DataDir: cfg.DataDir,
61 LogLevel: cfg.LogLevel,
62 BlockCacheMB: cfg.BlockCacheMB,
63 IndexCacheMB: cfg.IndexCacheMB,
64 QueryCacheSizeMB: cfg.QueryCacheSizeMB,
65 QueryCacheMaxAge: cfg.QueryCacheMaxAge,
66 QueryCacheDisabled: cfg.QueryCacheDisabled,
67 SerialCachePubkeys: cfg.SerialCachePubkeys,
68 SerialCacheEventIds: cfg.SerialCacheEventIds,
69 ZSTDLevel: cfg.ZSTDLevel,
70 }
71
72 // Initialize Badger database
73 log.I.F("initializing Badger database at %s", cfg.DataDir)
74 db, err := database.NewWithConfig(ctx, cancel, dbCfg)
75 if chk.E(err) {
76 log.E.F("failed to initialize database: %v", err)
77 os.Exit(1)
78 }
79
80 // Wait for database to be ready
81 log.I.F("waiting for database to be ready...")
82 <-db.Ready()
83 log.I.F("database ready")
84
85 // Reconcile any orphaned blob files (files on disk without metadata)
86 if reconciled, err := db.ReconcileBlobMetadata(); err != nil {
87 log.W.F("blob metadata reconciliation failed: %v", err)
88 } else if reconciled > 0 {
89 log.I.F("reconciled %d blob metadata entries", reconciled)
90 }
91
92 // Create and start gRPC server
93 serverCfg := &server.Config{
94 Listen: cfg.Listen,
95 LogLevel: cfg.LogLevel,
96 StreamBatchSize: cfg.StreamBatchSize,
97 }
98
99 srv := server.New(db, serverCfg)
100 if err := srv.ListenAndServe(ctx, cancel); err != nil {
101 log.E.F("gRPC server error: %v", err)
102 }
103 }
104
105 func loadConfig() *Config {
106 cfg := &Config{}
107 if err := env.Load(cfg, nil); chk.E(err) {
108 log.E.F("failed to load config: %v", err)
109 os.Exit(1)
110 }
111
112 // Set default data directory if not specified
113 if cfg.DataDir == "" {
114 home, err := os.UserHomeDir()
115 if chk.E(err) {
116 log.E.F("failed to get home directory: %v", err)
117 os.Exit(1)
118 }
119 cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY")
120 }
121
122 // Ensure data directory exists
123 if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) {
124 log.E.F("failed to create data directory %s: %v", cfg.DataDir, err)
125 os.Exit(1)
126 }
127
128 return cfg
129 }
130