pg-analytics-service is a deliberately compact analytics backend: one Go API, one PostgreSQL database, and one Redis instance. The design is centered on keeping report logic easy to review, recomputation explicit, and operational behavior visible in durable state.
The main runtime paths are:
- public report discovery and execution over precomputed snapshots
- management-triggered recomputation of snapshot ranges
- audit and health visibility for operational inspection
flowchart LR
Client["Public client"] --> HTTP["HTTP handlers (`internal/http`)"]
Operator["Management operator"] --> HTTP
HTTP --> Reports["ReportService"]
HTTP --> Recompute["RecomputeService"]
HTTP --> Audit["AuditService"]
HTTP --> Health["HealthService"]
Reports --> PG["PostgreSQL"]
Reports --> Redis["Redis"]
Recompute --> Lock["Redis scope lock"]
Recompute --> RunRow["`recompute_runs` row"]
Recompute --> Queue["In-process queue"]
Queue --> Worker["Worker goroutine"]
Worker --> PG
Worker --> Redis
Audit --> PG
Health --> PG
Health --> Redis
| Component | Primary code location | Responsibility |
|---|---|---|
| API entrypoint | cmd/api |
Loads config, opens PostgreSQL and Redis clients, optionally migrates and seeds, starts HTTP server and worker goroutines. |
| HTTP transport | internal/http |
Routes /api/v1, parses query and JSON inputs, applies timeouts, enforces management auth, and maps application errors into JSON responses. |
| Report service | internal/application/report_service.go |
Validates query scope, reads report definitions, serves cached results when possible, and executes snapshot queries on cache miss. |
| Recompute service | internal/application/recompute_service.go |
Validates recompute scope, acquires Redis locks, creates run records, enqueues work, updates run status, bumps cache version, and writes audit events. |
| PostgreSQL store | internal/infrastructure/postgres |
Owns schema, seed data, report catalog queries, report execution SQL, recompute SQL, run persistence, and audit persistence. |
| Redis store | internal/infrastructure/redis |
Owns report cache reads and writes, per-report cache version keys, and expiring lock acquisition and release. |
- A client calls
GET /api/v1/reports/{slug}/run. - The handler parses
window,date_from,date_to,limit,offset, and optional breakdown or filter parameters. ReportServicevalidates the window and range. If dates are omitted, the HTTP layer defaults to the last 30 days.- The service loads the report definition from PostgreSQL and resolves the cache TTL.
- If caching is enabled, Redis is checked using a key derived from the report slug, current cache version, and normalized request parameters.
- On cache miss, PostgreSQL queries
metric_snapshotsand returns report rows. - The result is serialized back into Redis and returned with execution metadata.
- A management client calls
POST /api/v1/recomputations. - The handler requires a management key and validates the JSON body.
RecomputeServicevalidates the requested report, window, and bounded date range.- Redis attempts to acquire a scope lock keyed by report slug, window, and date range.
- A
recompute_runsrow is inserted withpendingstatus, and an audit entry records the trigger event. - The run is pushed onto an in-process buffered channel.
- A worker goroutine marks the run
running, deletes the affected snapshot range, rebuilds it fromanalytics_events, and then marks the runcompletedorfailed. - On success, Redis increments the report cache version; on failure, the error is stored on the run and written to the audit trail.
GET /api/v1/healthchecks PostgreSQL and Redis independently. If either dependency is unavailable, the endpoint returns a degraded status and the HTTP status becomes503 Service Unavailable.GET /api/v1/audit-entriesis management-only and reads the persisted operational history from PostgreSQL.
| Store | Durable | What it owns |
|---|---|---|
| PostgreSQL | Yes | analytics_events, event_sources, report_definitions, aggregate_windows, metric_snapshots, recompute_runs, and audit_entries |
| Redis | No | report-response cache payloads, per-report cache versions, and recompute locks |
This split is intentional. Analytical truth, report metadata, and operational history remain in PostgreSQL. Redis accelerates or protects workflows, but never becomes the system of record.
The project is intentionally PostgreSQL-centered for both data and architecture reasons:
- Recompute SQL stays explicit and readable, which is important in a portfolio repository where reviewers will inspect the actual aggregation logic.
date_trunc('day'|'week', occurred_at)gives a direct and predictable bucket model for snapshot generation.- Snapshot reads avoid repeatedly grouping raw events on every API request.
- Run history and audit trail stay transactionally close to the data model they describe.
- Indexes on time, source, status, entity, run status, and audit timestamps match the dominant query paths.
| Decision | Benefit | Trade-off |
|---|---|---|
| In-process recompute queue | Very small operational footprint and straightforward local setup | No durable backlog across API restarts |
| Full-range snapshot rebuild per requested scope | Deterministic and easy to reason about | More work than incremental recompute at larger scale |
| Redis version-bump invalidation | No wildcard delete or cache-key scanning | Old cache generations expire naturally rather than being actively removed |
| Single management API key | Simple, explicit control surface | Not a substitute for tenant-aware or role-based authorization |
Roadmap context: Roadmap