second-brain/postgres-migration-planning.md
2026-05-24 22:23:59 -04:00

4.2 KiB

Postgres + pgvector migration — planning questions

Status: awaiting input from Travis before scoping the migration. Trigger: user pivoted away from SQLite — wants to use the existing Postgres + pgvector instance, plus the shared /projects/shared/python/embedding_chunking module.


1. Connection / secrets

  • Host, port, database name, user
  • Where does the connection string live? Options:
    • env var (e.g. SECOND_BRAIN_DB_URL)
    • config/settings.toml (would need a .local.toml override so the password doesn't get committed)
    • .pgpass
    • other
  • Is the Postgres instance on herbydev, the NAS, or elsewhere? Tailscale-only or LAN-reachable?
  • Driver preference:
    • psycopg v3 sync (default suggestion — pipeline + scheduler are sync today)
    • asyncpg (only worth it if we also rewrite the web layer to async)

2. Schema layout

  • Same Postgres instance as vault-mcp's petalbrain.wiki? Yes / No
  • If yes — same database with a dedicated schema (e.g. second_brain.sources, second_brain.extractions)? Or its own database entirely?
  • Naming conventions to match:
    • snake_case tables — assumed yes
    • created_at / updated_at as timestamptz (vs current naive UTC) — preference?
    • PK type: bigserial / bigint identity / UUID?
  • Should second-brain integrate with vault-mcp at all?
    • Option A: standalone — second-brain owns its tables, the wiki compiler still writes Obsidian markdown files
    • Option B: dual-write — published extractions land in petalbrain.wiki via vault-mcp so they show up in the unified wiki
    • Option C: replace the file-based compiler entirely with vault-mcp writes

3. Embeddings

  • Public API of /projects/shared/python/embedding_chunking:
    • function signatures
    • what it returns (raw text chunks? chunks + vectors? chunker only, embeddings done separately?)
    • embedding model + vector dimension (drives the vector(N) column type)
    • any batching / rate-limit behavior we need to respect
  • Where should embeddings attach?
    • (a) on Extraction rows — semantic search over summaries / key_points / claims
    • (b) on chunked transcripts — chunk-level retrieval for "find the part of the video that says X"
    • (c) both (separate tables: extraction_embeddings + transcript_chunks)
  • pgvector index strategy:
    • HNSW — better recall, slower build, recommended default for personal-scale corpus
    • IVFFlat — faster build, needs periodic ANALYZE + a meaningful lists value
  • Distance metric: cosine (typical), L2, or inner product?

4. Migration tooling

  • Schema is small and not yet in production. Cleanest path:
    • SQLAlchemy Base.metadata.create_all() against Postgres
    • one-off import script to move existing SQLite rows over (if any are worth keeping)
  • Alternative: stand up Alembic now while the cost is low. Pays off the first time you ALTER a table in anger; costs ~1 session of setup.
  • Travis's call — defaulting to create_all + import script unless you say otherwise.

5. Pooling

  • Three consumers share the DB: CLI (process, compile), the FastAPI web server, the scheduler.
  • Default plan: SQLAlchemy QueuePool with pool_size=5, max_overflow=5.
  • Is PgBouncer already in front of this instance? If yes — switch to NullPool on our side and let PgBouncer pool.

6. Other open items (carryover from prior session)

  • Vault location decision: I recommended /opt/projects/second-brain-vault/ as a peer dir. Not yet confirmed.
  • NAS mount: is it mounted on herbydev, and at what path? Relevant if media (downloaded videos, transcripts) should live there rather than on the herbydev SSD.

What I will produce once these are answered

  1. Updated src/second_brain/database.py with a Postgres engine + URL resolution
  2. Schema migration: new tables for embeddings (and chunks if we go with option (c))
  3. Wiring of embedding_chunking into the extractor (or into a post-extraction step in the scheduler)
  4. config/settings.toml.example updates with the new [database] and [embeddings] blocks
  5. README / CLAUDE.md updates noting the Postgres dependency
  6. One-shot SQLite → Postgres import script (only if there are rows worth keeping)