{"record":{"id":"acb4405933dc0aee","repo":"Billionmail/BillionMail","slug":"failed-to-get-option-from-database","errorCode":null,"errorMessage":"failed to get option from database: ","messagePattern":"failed to get option from database: ","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/internal/service/public/options_mgr.go","lineNumber":86,"sourceCode":"\n\t// Try to get from cache\n\tcacheKey := o.buildCacheKey(key)\n\tcached, err := o.cache.Get(ctx, cacheKey)\n\tvar jsonValue string\n\n\tif err != nil || cached == nil {\n\t\t// Cache miss, read from database\n\t\tvar result struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\n\t\terr := g.DB().Model(\"bm_options\").\n\t\t\tWhere(\"name\", key).\n\t\t\tFields(\"value\").\n\t\t\tScan(&result)\n\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to get option from database: \" + err.Error())\n\t\t}\n\n\t\tif result.Value == \"\" {\n\t\t\treturn errors.New(\"option not found\")\n\t\t}\n\n\t\tjsonValue = result.Value\n\n\t\t// Store in cache\n\t\tif err := o.cache.Set(ctx, cacheKey, jsonValue, o.expiration); err != nil {\n\t\t\tg.Log().Warning(ctx, \"Failed to set option cache:\", err)\n\t\t}\n\t} else {\n\t\t// Cache hit\n\t\tjsonValue = cached.String()\n\t}\n\n\t// Deserialize","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/public/options_mgr.go#L68-L104","documentation":"When the value is not in cache, GetOption queries the bm_options table (WHERE name = key). Any database error returned by the ORM is wrapped as \"failed to get option from database: <driver error>\". This signals an infrastructure/DB problem or a schema issue, not a missing option (that's the separate 'option not found' error).","triggerScenarios":"Database is down/unreachable, bm_options table missing (schema not migrated), or connection pool exhausted when GetOption falls through to the DB after a cache miss.","commonSituations":"Fresh deployment before migrations run; Postgres restarted or container not up; wrong DB credentials in config causing connection failures.","solutions":["Read the wrapped driver message after the colon to identify the root cause (connection refused, relation does not exist, etc.).","Verify PostgreSQL is running and reachable (docker compose ps, connection settings).","Ensure schema migrations have created the bm_options table.","Check Redis cache health — repeated DB fallbacks due to cache outages amplify DB load."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// ping DB before batch option loads\nif err := g.DB().PingContext(ctx); err != nil {\n    return fmt.Errorf(\"database unavailable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := public.GetOption(ctx, key, &out); err != nil {\n    if strings.Contains(err.Error(), \"failed to get option from database\") {\n        // transient DB issue: retry with backoff\n        return retry.Do(3, 500*time.Millisecond, func() error {\n            return public.GetOption(ctx, key, &out)\n        })\n    }\n    return err\n}","preventionTips":["Monitor PostgreSQL health and alert on connection failures.","Run migrations before app startup so bm_options always exists.","Keep Redis cache healthy to reduce DB fallbacks.","Distinguish DB errors from 'option not found' by matching the message prefix."],"tags":["database","postgres","options"],"backgroundTag":"database-query-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}