diff --git a/cmd/synapbus/main.go b/cmd/synapbus/main.go index 232d0f0..a95f16c 100644 --- a/cmd/synapbus/main.go +++ b/cmd/synapbus/main.go @@ -865,6 +865,7 @@ func runServe(cmd *cobra.Command, args []string) error { Broadcaster: sseBroadcaster, SessionMiddleware: sessionMiddleware, DB: db.DB, + ReadDB: db.QueryDB(), Version: version, PushService: pushService, TrustService: trustService, diff --git a/internal/api/analytics_handler.go b/internal/api/analytics_handler.go index 303d68f..45e4067 100644 --- a/internal/api/analytics_handler.go +++ b/internal/api/analytics_handler.go @@ -21,6 +21,8 @@ type AnalyticsHandler struct { } // NewAnalyticsHandler creates a new analytics handler. +// db should be the read pool — every query in this handler is read-only and +// must not block on the serialized write pool. func NewAnalyticsHandler(db *sql.DB, agentService *agents.AgentService, channelService *channels.Service) *AnalyticsHandler { return &AnalyticsHandler{ db: db, diff --git a/internal/api/router.go b/internal/api/router.go index f788be9..517c16c 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -52,8 +52,12 @@ type RouterConfig struct { Broadcaster *SSEBroadcaster SessionMiddleware func(http.Handler) http.Handler DB *sql.DB - Version string - BaseURL string + // ReadDB is the read-only connection pool (query_only=ON, MaxOpenConns>1). + // Read-only API handlers must use this to avoid blocking on the serialized + // write pool. If nil, handlers fall back to DB. + ReadDB *sql.DB + Version string + BaseURL string // CoreMemoryStore (feature 020 — US2) wires the per-(owner, agent) // core memory REST endpoints. Nil → routes not registered. @@ -324,9 +328,14 @@ func NewRouterWithConfig(cfg RouterConfig) chi.Router { }) } - // Analytics (authenticated, requires DB) + // Analytics (authenticated, requires DB). Reads must use the read pool so + // dashboard queries don't serialize behind long-running writers. if cfg.DB != nil { - analyticsHandler := NewAnalyticsHandler(cfg.DB, cfg.AgentService, cfg.ChannelService) + analyticsDB := cfg.ReadDB + if analyticsDB == nil { + analyticsDB = cfg.DB + } + analyticsHandler := NewAnalyticsHandler(analyticsDB, cfg.AgentService, cfg.ChannelService) r.Group(func(r chi.Router) { r.Use(authMiddleware)