feat: use postgres

This commit is contained in:
Ilia Denisov
2026-04-26 20:34:39 +02:00
committed by GitHub
parent 48b0056b49
commit fe829285a6
365 changed files with 29223 additions and 24049 deletions
+144
View File
@@ -13,6 +13,149 @@ Execution priorities:
- Defer threshold tuning until after the basic data model is working.
- Avoid unnecessary infrastructure on the first iteration.
## Stage 00 — Persistence Stack and Backend Assignment
Goal:
- Pin the platform-wide persistence stack and the per-service backend
ownership before any feature stage begins, so that subsequent stages
design schemas, queries, and worker loops consistently with the
project-wide rules in
[`../ARCHITECTURE.md §Persistence Backends`](../ARCHITECTURE.md#persistence-backends)
and the staged migration plan in
[`../PG_PLAN.md`](../PG_PLAN.md).
This stage is documentation-only: no code exists in this service yet, and
this stage adds none. It is a prerequisite to every later stage and ships
as part of `PG_PLAN.md` Stage 8.
Tasks:
- Adopt the shared Postgres helper [`pkg/postgres`](../pkg/postgres) for
every durable storage path:
- driver `github.com/jackc/pgx/v5`, exposed as `*sql.DB` via
`github.com/jackc/pgx/v5/stdlib`;
- query layer `github.com/go-jet/jet/v2` (PostgreSQL dialect) with
generated code under `internal/adapters/postgres/jet/`, regenerated
by a per-service `make jet` target and committed to the repo;
- migrations via `github.com/pressly/goose/v3` library API embedded
with `//go:embed`, applied at service startup before any HTTP
listener becomes ready, with non-zero exit on failure;
- `github.com/testcontainers/testcontainers-go` (`modules/postgres`)
for unit tests and for hosting the transient instance used by
`make jet`.
- Adopt the shared Redis helper [`pkg/redisconn`](../pkg/redisconn) for
every Redis client:
- master/replica/password connection shape;
- mandatory password;
- no `TLS_ENABLED`, no `USERNAME` (rejected at startup with a clear
error from `pkg/redisconn.LoadFromEnv`).
- Own the `geoprofile` schema in the shared `galaxy` PostgreSQL database.
Connect with a dedicated `geoprofile` PG role whose grants are
restricted to its own schema (defense-in-depth, expressed in the
initial migration).
- Lay out the postgres-backed adapter directory consistently with the
PG-migrated services:
```text
geoprofile/
internal/
adapters/
postgres/
migrations/ # *.sql files + migrations.go (//go:embed)
jet/ # generated code, commit-checked
<storeName>/ # adapter implementations matching
# internal/ports
config/
config.go # Postgres + Redis schemas
Makefile # `jet` target: testcontainers + goose + jet
```
- Backend assignment for the entities listed in
[`README.md §Data Entities`](README.md#data-entities):
- PostgreSQL (`geoprofile` schema, source of truth):
- `country_observation` — durable observed-country fact rows.
- `device_session_country_score` — per-`device_session_id` weighted
country aggregates.
- `device_session_geo_state` — current `usual_connection_country`
per `device_session_id`.
- `user_review_state` — `country_review_recommended` flag and last
evaluation timestamp.
- `declared_country_version` — immutable history of approved
`declared_country` changes (with version status `recorded` /
`applied` / `sync_failed`).
- `session_block_action` — local audit of block-request outcomes.
- Ingest-queue lifecycle from §Stage 05 (`accepted` / `processing` /
`processed` / `failed`) is materialised as `status` /
`next_attempt_at` columns on the durable observation row, not as a
Redis ZSET. Workers select pending work via
`SELECT ... FOR UPDATE SKIP LOCKED`, mirroring the pattern already
in use by Mail and Notification.
- Redis (`pkg/redisconn`):
- only ephemeral runtime-coordination signals if any appear during
implementation — for example, transition-deduplication windows for
review-flag notifications, short worker leases on processing
claims. No durable business state.
- the `notification:intents` Redis Stream is used by this service
only as a producer to publish `geo.review_recommended` intents
(see §Stage 11 and `README.md §Integration with Notification
Service`); that connection is built via `pkg/redisconn`.
- **Idempotency**, if added for ingest deduplication, is a `UNIQUE`
constraint on the durable observation row, never a separate Redis kv.
**Retry scheduling**, if added for worker reprocessing or
`User Service` sync retries, is a column on the durable record, worked
off via `FOR UPDATE SKIP LOCKED`. Both rules align this service with
the platform-wide pattern.
- Time-valued columns are `timestamptz`. Adapters normalise every
`time.Time` value crossing the SQL boundary to `time.UTC` on bind and
scan, per
`../ARCHITECTURE.md §Persistence Backends — Timestamp handling`.
- Configuration (target):
- PostgreSQL knobs (loaded via
`pkg/postgres.LoadFromEnv("GEOPROFILE")`):
- `GEOPROFILE_POSTGRES_PRIMARY_DSN` (required;
`postgres://geoprofile:<pwd>@<host>:5432/galaxy?search_path=geoprofile&sslmode=disable`);
- `GEOPROFILE_POSTGRES_REPLICA_DSNS` (optional, comma-separated;
reserved for future read-routing, not consumed yet);
- `GEOPROFILE_POSTGRES_OPERATION_TIMEOUT`,
`GEOPROFILE_POSTGRES_MAX_OPEN_CONNS`,
`GEOPROFILE_POSTGRES_MAX_IDLE_CONNS`,
`GEOPROFILE_POSTGRES_CONN_MAX_LIFETIME`.
- Redis knobs (loaded via
`pkg/redisconn.LoadFromEnv("GEOPROFILE")`):
- `GEOPROFILE_REDIS_MASTER_ADDR` (required),
`GEOPROFILE_REDIS_REPLICA_ADDRS` (optional, comma-separated);
- `GEOPROFILE_REDIS_PASSWORD` (required);
- `GEOPROFILE_REDIS_DB`,
`GEOPROFILE_REDIS_OPERATION_TIMEOUT`.
- Per-service decision record `geoprofile/docs/postgres-migration.md`
is created by the stage that actually implements the service. It must
capture: schema and role grants, queue materialisation choice, retry
pattern, and any non-trivial deviation from the platform-wide rules
(analogous to
[`../user/docs/postgres-migration.md`](../user/docs/postgres-migration.md),
[`../mail/docs/postgres-migration.md`](../mail/docs/postgres-migration.md),
[`../notification/docs/postgres-migration.md`](../notification/docs/postgres-migration.md),
and [`../lobby/docs/postgres-migration.md`](../lobby/docs/postgres-migration.md)).
Exit criteria:
- The persistence stack and schema ownership are fixed and visible to
implementers.
- Every later stage (Stage 01+) designs schemas and queries on top of
the `geoprofile` Postgres schema, or — for any ephemeral signal — on
top of `pkg/redisconn`.
- `../ARCHITECTURE.md §Persistence Backends` and `../PG_PLAN.md` remain
the canonical references; this PLAN points at them rather than
duplicating their content.
## Stage 01 — Freeze Service Vocabulary and Contracts
Goal:
@@ -643,6 +786,7 @@ Exit criteria:
Recommended delivery order:
- Persistence stack and backend assignment
- Domain vocabulary and ownership
- Domain model
- FlatBuffers schema