You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: record Day 82 — free downgrade, lean mode executed, cron-log purge
DEVLOG Day 82: the org was downgraded Supabase Pro -> free, lean mode
was executed (470 -> 171 MB, embeddings + HNSW + hybrid RPC dropped,
JOB_SEARCH_HYBRID_ENABLED=false, search now lexical-only, all 14,083
jobs intact), the landing copy was corrected to drop the now-false
semantic/hybrid claims (commit 470e62f), and a daily
purge-cron-run-history pg_cron job was added (cleared 29,156 stale
rows). Also documents the corrected lesson: a plain VACUUM is NOT a
durable lever here (the churn re-bloats ~90 MB/day back to the ~485 MB
high-water-mark, not 30-60 MB/month as Day 81 wrongly claimed).
deployment.md:
- scheduled-jobs inventory: add purge-cron-run-history; note
embed-on-write is OFF under lean mode.
- hybrid-search lean/full runbook: flipped from "on Pro, lean is
hypothetical" to "CURRENT STATE: on free, lean ACTIVE"; the full/
hybrid half is now the restore-on-Pro path. Corrected the wrong
"VACUUM buys 30-60 MB/month" claim.
architecture.md: note that prod runs lean (embedding column + HNSW +
hybrid RPC dropped, flag false, lexical-only search) as of 2026-06-23;
the hybrid capability remains in code as a restore-on-Pro operation.
(AGENT.md §3 + §6 updated locally too — free plan, lean active, ~171 MB,
the new cron job; untracked working briefing, not in this commit.)
Copy file name to clipboardExpand all lines: docs/architecture.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -217,7 +217,7 @@ Each `saved_jobs` row stores one shortlisted posting per user and normalized job
217
217
218
218
Each `resume_builder_sessions` row stores one in-progress conversational resume-builder draft per user with a 7-day TTL refreshed on every save. A `pg_cron` job (`cleanup-expired-resume-builder-sessions`) hard-deletes expired rows every 5 min and RLS hides expired rows from per-user queries; see [ADR-016](adr/ADR-016-conversational-llm-resume-builder.md).
219
219
220
-
Each `cached_jobs` row holds one upstream posting keyed on `(source, job_id)`. The table has GENERATED STORED columns (`work_mode`, `employment_type_norm`) backing the dropdown filters, `removed_at` tombstones for upstream-closed jobs the user has bookmarked, and an `embedding vector(1536)` column (pgvector, HNSW cosine index) for semantic search. A `pg_cron` + `pg_net` schedule (`cached_jobs_refresh_4h`) POSTs to `/admin/refresh-cache` every 4 hours, six times a day (see `docs/sql/job_cache_cron_setup.sql` for the template — production runs `0 */4 * * *`). Search is two-tier: the lexical `search_cached_jobs_ranked` RPC ([ADR-014](adr/ADR-014-postgres-rpc-for-ranked-search.md)) and the hybrid `search_cached_jobs_hybrid` RPC, which fuses that lexical ranking with a pgvector semantic ranking via Reciprocal Rank Fusion. The hybrid path is gated behind the `JOB_SEARCH_HYBRID_ENABLED` flag and degrades to lexical on any failure; see [ADR-033](adr/ADR-033-hybrid-job-search.md).
220
+
Each `cached_jobs` row holds one upstream posting keyed on `(source, job_id)`. The table has GENERATED STORED columns (`work_mode`, `employment_type_norm`) backing the dropdown filters, `removed_at` tombstones for upstream-closed jobs the user has bookmarked, and an `embedding vector(1536)` column (pgvector, HNSW cosine index) for semantic search. A `pg_cron` + `pg_net` schedule (`cached_jobs_refresh_4h`) POSTs to `/admin/refresh-cache` every 4 hours, six times a day (see `docs/sql/job_cache_cron_setup.sql` for the template — production runs `0 */4 * * *`). Search is two-tier: the lexical `search_cached_jobs_ranked` RPC ([ADR-014](adr/ADR-014-postgres-rpc-for-ranked-search.md)) and the hybrid `search_cached_jobs_hybrid` RPC, which fuses that lexical ranking with a pgvector semantic ranking via Reciprocal Rank Fusion. The hybrid path is gated behind the `JOB_SEARCH_HYBRID_ENABLED` flag and degrades to lexical on any failure; see [ADR-033](adr/ADR-033-hybrid-job-search.md). **As of 2026-06-23, production runs in LEAN MODE (Supabase free-tier downgrade): the `embedding` column + HNSW index + hybrid RPC are DROPPED in prod and the flag is `false`, so live search is lexical-only. The hybrid capability remains in the codebase + the SQL files; it's a restore-on-Pro operation, not a deleted feature. See the "Hybrid-search lean/full switch" runbook in [deployment.md](deployment.md) and DEVLOG Day 82.**
221
221
222
222
`aijobagent_run_traces` is an append-only cost-attribution table — one row per successful LLM call (`user_id`, `model`, `task`, `prompt_tokens`, `completion_tokens`, `cost_usd`, `created_at`). Writes are best-effort: a missing table or a write error never propagates to the user-facing path. It is the canonical answer to "what is OpenAI spend doing", separate from the Sentry/PostHog telemetry surface.
Copy file name to clipboardExpand all lines: docs/deployment.md
+36-33Lines changed: 36 additions & 33 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,13 +9,14 @@ into `architecture.md` or an ADR lives here.
9
9
10
10
This is the authoritative list of everything that runs on a schedule
11
11
in production. **No scheduled job runs a chat / completion model.**
12
-
The cache refresh does spend a small amount on *embeddings* — see the
13
-
`cached_jobs_refresh` row. Audited 2026-05-22.
12
+
Audited 2026-05-22; updated 2026-06-23 (lean-mode downgrade + the new
13
+
`purge-cron-run-history` job).
14
14
15
15
| Job | Where | Schedule | What it does | LLM cost |
16
16
| --- | --- | --- | --- | --- |
17
-
|`cached_jobs_refresh`| Supabase `pg_cron` + `pg_net`| every 4h (`0 */4 * * *`, six runs/day) | POSTs `/api/admin/refresh-cache` so the backend re-polls the Greenhouse/Lever/Ashby/Workday boards into `cached_jobs`|**~\$0** — job-board APIs are free; embeds only *newly-cached* jobs with `text-embedding-3-small`when `JOB_SEARCH_HYBRID_ENABLED` is on (a few cents/day). No chat-model call |
17
+
|`cached_jobs_refresh`| Supabase `pg_cron` + `pg_net`| every 4h (`0 */4 * * *`, six runs/day) | POSTs `/api/admin/refresh-cache` so the backend re-polls the Greenhouse/Lever/Ashby/Workday boards into `cached_jobs`|**\$0**now — job-board APIs are free, and **embed-on-write is OFF since the 2026-06-23 lean-mode downgrade** (`JOB_SEARCH_HYBRID_ENABLED=false`, embedding column dropped). When the project is back on Pro + hybrid is re-enabled, this row embeds newly-cached jobs with `text-embedding-3-small` (a few ¢/day). No chat-model call either way|
18
18
|`cleanup-expired-resume-builder-sessions`| Supabase `pg_cron`| every 5 min (`*/5 * * * *`) | Hard-deletes `resume_builder_sessions` rows past their 7-day TTL |**\$0** — plain SQL `DELETE`, no LLM |
19
+
|`purge-cron-run-history`| Supabase `pg_cron`| daily (`30 3 * * *`) |`DELETE FROM cron.job_run_details WHERE end_time < now() - interval '7 days'`. Bounds pg_cron's own run-history table, which grows unboundedly (the 5-min resume-builder cleanup alone adds ~288 rows/day). Added 2026-06-23 after it had reached ~30k stale rows |**\$0** — plain SQL `DELETE`, no LLM |
19
20
|`backend.maintenance` (saved-workspace retention sweep) | VPS host crontab — **verify against prod**| daily (`0 3 * * *` assumed) | Runs `python -m backend.maintenance` → `sweep_expired_workspaces`: deletes saved workspaces past their per-tier retention (Free > 7d, Pro > 30d). Now check-ins to the `saved-workspaces-retention` Sentry cron monitor (M22) |**\$0** — plain `DELETE`, no LLM |
20
21
|`backend.nightly_eval`| VPS host crontab |**NOT INSTALLED**| Would run the LLM quality eval; deliberately not scheduled | would be ~\$0.25/run if enabled |
21
22
@@ -273,36 +274,38 @@ botched drop can't lose the exact index config. The lean-mode script
273
274
only ever drops the *semantic* add-ons (embedding column + HNSW); it
274
275
never touches the base table or the lexical indexes.
275
276
276
-
**IMPORTANT — the 500 MB cap is NOT a current constraint.** The
277
-
jobagent Supabase org (`Job_Application_Copilot`) is on the **Pro plan
278
-
(8 GB database, daily backups, no idle auto-pause)**. So none of the
279
-
"over the cap" framing below is a live emergency — it all describes the
280
-
*future* scenario where the org is downgraded **Pro → free** to save the
281
-
~$25/mo (no users yet). The lean/full switch + the VACUUM lever exist to
282
-
make that downgrade possible and survivable, not to rescue a current
283
-
overage.
284
-
285
-
**Live storage check + VACUUM (jobagent prod, 2026-06-23):** the DB was
286
-
**504 MB** (fine on Pro's 8 GB, but over the *free*-tier 500 MB number).
287
-
`cached_jobs` was 485 MB of that (14,085 rows, all embedded): heap+TOAST
288
-
335 MB, indexes 150 MB; the semantic layer alone = the `embedding`
289
-
column (83 MB live) + the HNSW index (110 MB) = **193 MB**. The
290
-
heap+TOAST (335 MB) far exceeded the live column data (~220 MB) — ~115
291
-
MB of dead-tuple bloat from the 4-hourly refresh churn (autovacuum
292
-
reclaims-for-reuse but never shrinks the file). A **`VACUUM FULL
293
-
public.cached_jobs` was run** to reclaim it: **504 → 381 MB** (−123 MB;
294
-
heap+TOAST 335→241, indexes 150→121). No data lost, semantic search
295
-
intact. This made the project **free-downgrade-eligible** (free limits
296
-
all clear: DB 381/500 MB, storage 0/1 GB, MAU 0/50k, 1 project/org).
297
-
298
-
Two levers, two purposes:
299
-
-**Standalone `VACUUM FULL`** (what was run) — reclaims churn bloat,
300
-
keeps semantic search. ~2-min ACCESS EXCLUSIVE lock. The churn
301
-
re-accumulates ~30-60 MB/month, so it's a periodic reset (re-run
302
-
when the DB nears ~480 MB), not a permanent fix.
303
-
-**Lean mode** (the switch above) — drops embeddings + HNSW to ~300 MB
304
-
durably; loses semantic concept-matching. The fallback for staying
305
-
under 500 MB long-term without re-VACUUMing or paying for Pro.
277
+
**CURRENT STATE (2026-06-23): on the FREE plan, LEAN MODE ACTIVE.** The
278
+
jobagent Supabase org was downgraded **Pro → free** to cut the ~$25/mo
279
+
pre-revenue, and lean mode was executed to fit the 500 MB cap. So job
280
+
search is **lexical-only right now** — the "full / hybrid" half below is
281
+
the *restore* path for if/when the project goes back to Pro, not the
0 commit comments