docs(DEPLOY-004): Postgres worked guide + pool_pre_ping for server DBs#104
Conversation
docs/operations.md already said "set DATABASE_URL for Postgres" but left
every operational step to the reader. The Database section now carries:
- a worked end-to-end self-hosted example: provision (container or package
server, least-privilege role), DATABASE_URL, schema via the automatic
startup migration or manual `python -m alembic upgrade head`, and a
verification checklist (alembic_version, \dt, Scan history, Admin health);
- a SQLite -> Postgres data-migration recipe for existing history: stop
writers, build the target schema with Alembic first (never copy
alembic_version), copy rows in Base.metadata.sorted_tables order with the
session pinned to UTC (SQLite stored naive-UTC timestamps), then bump
every serial sequence past the copied max ids - the classic duplicate-key
trap; verify counts before flipping DATABASE_URL, keep the SQLite file as
the rollback copy;
- connection-pool guidance: default pool sizing is ample for this app;
PgBouncer session pooling as the conservative default.
Code rider: _make_engine now sets pool_pre_ping=True for any non-SQLite URL.
Managed Postgres proxies / PgBouncer / cloud NAT idle-kill TCP connections,
so the first scan after a quiet stretch used to inherit a dead pooled
connection ("server closed the connection unexpectedly"). The ping is one
lightweight round-trip per checkout. SQLite engine arguments are
byte-identical (locked by the new test alongside the existing pragma tests).
The CI command strings and scheduler/database contracts asserted verbatim by
tests/test_supply_chain_policy.py are untouched (test passing proves it).
Gates: 1,387 passed, coverage 88.13% (floor 87); pre-commit validate,
compileall, ruff, mypy (119 files), bandit, pip-audit all clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DoRmAmMu1997
left a comment
There was a problem hiding this comment.
Self-review (posted as COMMENT — the bot runs as the PR author and cannot APPROVE). Verdict: Approve.
- Rider isolation — the diff to
_make_engineis oneelsearm addingpool_pre_ping=True; the SQLite branch (connect_args, pragma listener) is character-identical. The new test assertspool._pre_ping is Falsefor SQLite andis Truefor apostgresql+psycopg://engine — built lazily, so no server is needed and the pinned psycopg 3 driver only has to import. All 8 storage-database tests pass. - Migration recipe verified against the code, not written from memory —
Base.metadata.sorted_tablesyields FK parents first (scan_results→scan_runs, IPO children→ipo_issues);alembic_versionis not inBase.metadata, so the loop cannot copy it even if an operator forgets the warning; the UTC session pin matches the repository's_utcnote that SQLite persists naive-UTC while Postgres preserves offsets; the serial-sequence reset guards withpg_get_serial_sequence IS NOT NULLso PK-less or non-serial tables are skipped cleanly. - Verbatim doc contracts intact —
tests/test_supply_chain_policy.pyassertsCRON_TZ=Asia/Kolkata, the host-timezone note, and the`psycopg[binary]`string in operations.md; the insert is purely additive between the existing schema paragraph and the Docker section, and the policy test passes. - No dependency motion — psycopg 3 was already pinned for
postgresql+psycopg://URLs; constraints/pyproject diff vs main is empty. - ID reuse flagged, not hidden — DEPLOY-004 also tags the earlier production-settings validation; the PR body notes this extends the same deployment epic.
- Docs render sanity — new subsections nest under the existing
## Databaseheading (three###blocks), fenced code blocks are bash/sql/python/env-tagged, and no Mermaid was touched.
Gates: 1,387 passed + 1 skipped, coverage 88.13% (floor 87); ruff/mypy(119)/bandit/pip-audit/pre-commit/compileall clean. Security review: no findings (placeholder credentials only; least-privilege provisioning; no new connection surface).
|
Codex review plan before fixes:
I’ll update the guide/tests, run the full docs/runtime/security/Docker gates, and push the follow-up to this branch. |
DoRmAmMu1997
left a comment
There was a problem hiding this comment.
Codex review findings for DEPLOY-004. The runtime pool_pre_ping change is sound; the operational examples need credential-safe handling and one schema-name correction.
Co-authored-by: Hemant <hemantdhamija@gmail.com> Co-authored-by: Codex <codex@openai.com>
Completion verdictComplete — no remaining reportable findings. This is a completion comment, not an approval. Commit A formal diff-security review found no remaining reportable findings. |
Ticket scope (DEPLOY-004 — Postgres worked guide, a previously-declined item now requested)
Goal: close the gap the June review flagged and the register deferred —
docs/operations.mdcovered Compose-with-Postgres, Render, and backups, but the core self-hosted path ("I have a machine and want shared Postgres history") and the data migration ("I already have months of SQLite history") were left entirely to the reader.In scope
docs/operations.md: provision (container or package-server route, least-privilege role — the app needs database ownership only, no superuser),DATABASE_URL, schema via the automatic startup migration or manualpython -m alembic upgrade head, and a concrete verification checklist (alembic_version,\dt, Scan history view, Admin health, ascan_runsspot query).Base.metadata.sorted_tablesorder (parents before FK children) into a schema Alembic built — and never copyalembic_version;Plus: stop writers first, verify counts before flipping, keep the SQLite file as the rollback copy.
max_connectionsfor instances, not vice-versa; PgBouncer session pooling as the conservative default.backend/storage/database.py:_make_enginenow setspool_pre_ping=Truefor any non-SQLite URL. Managed Postgres proxies / PgBouncer / cloud NAT idle-kill TCP connections, so the first scan after a quiet stretch inherited a dead pooled connection ("server closed the connection unexpectedly"). One lightweight round-trip per checkout buys transparent reconnection. SQLite engine arguments are byte-identical, locked by a new test next to the existing pragma tests (the Postgres side is asserted on a lazily-built engine — no server needed, the pinned psycopg 3 driver only has to import).Out of scope (deliberate)
constraints.txt/pyproject.tomldiff vs main is empty.tests/test_supply_chain_policy.pyare untouched — that test passing is the proof.app.py'smain()docstring); this PR extends the same deployment epic rather than opening a new ID mid-wave.This is ticket 7 of a 10-ticket second improvement wave (TEST-007 #98, REF-003 #99, AI-006 #100, IPO-006 #101, TEST-006 #102, PERF-002 #103, DEPLOY-004, QUAL-006, QUAL-007, DOC-003), each shipped as its own PR off
main.Gates (all green, local, Python 3.13)
pytest -q --cov=backend --cov=screeners --cov=ui --cov-fail-under=87tests/test_supply_chain_policy.py(verbatim doc-contract lock)pre_commit validate-config/compileall/ruff/mypy(119 files) /banditpip_audit -r constraints.txtReview
/code-review: Approve — the rider is minimal and branch-isolated (theelsearm only adds a kwarg; the SQLite arm is untouched, and the new test assertspool._pre_pingon both engine flavors); the migration recipe was checked against the ORM metadata (sorted_tables ordering, serial-sequence reset,alembic_versionexclusion) and the repository's_utcnaive-timestamp handling./security-review: no findings — the doc examples use placeholder credentials, the recipe requires no superuser, andpool_pre_pingadds no new connection surface (same URL, same driver).🤖 Generated with Claude Code