Repository navigation
Propose durable jobs on pg-boss as the queue the knowledge database builds on - #179
Conversation
nlundee
left a comment
There was a problem hiding this comment.
This is what we asked for on #125, and it reads well. The claims about today's code hold on main (failStaleRuns skipping only the instance's own runs, the 6-hour sweep, the 2-minute lease, the in-process writer locks), and the retention, keys-only payloads and "one attempt, the queue retries" answer the points from the #125 review. Close to approve, one thing to settle first:
- The definer function isn't the only way in (
0041, Consequences, and the new line in0022).genai_runtimegets DML on thepgbosstables, so the API canSELECTfrompgboss.jobdirectly, across organisations, withoutwithPlatformAccess. Either grant the runtime only whatsend()needs, so theSECURITY DEFINERfunction really is the only read path, or say plainly that it's a convention and not enforced by the database. With keys-only payloads the impact is small, but 0022 should describe what the database actually enforces.
Smaller:
- #100 still says in its Why section that the wait for scores "is a durable job" (proposed in #125), which contradicts both this record and its own "No durable jobs yet". Worth fixing there so the two don't disagree. #100 and #117 also both use 0040.
- A handler should take the organisation from the record it loads, never from the job data. It follows from "keys, never content", but one sentence (and a test in the list) would make it a rule.
- The Context motivates work that lasts several nights, while a job can't run past 24 hours. One line saying long work is split into jobs under that limit, with progress in its record, would close the gap. #125 does this per queue already.
- The PR text says "Built in #155", the record says "Not built (issue 155)". I read #155 as the tracking issue, so maybe "Tracked in #155".
Fine to approve once the first point is settled.
The review of #179 found that genai_runtime got DML on the pgboss tables, so the API could read, change or delete every organisation's jobs without platform access. - The worker runs as genai_worker, a member of genai_runtime with DML on pgboss. genai_runtime gets USAGE and EXECUTE on the read functions only, and a queue the API sends to grants only what sending needs. - Sending dead letters back goes through the worker. - A handler takes the organisation from its record, never the job. - Work longer than 24 hours is split into jobs, with progress in its record. - ADR 0022's line says the grants enforce it.
- The API reads job state through a JobReads port that does not start pg-boss, since genai_runtime cannot read pgboss.version. - The test list says which tests come with the first real queue (issue 135) and the console screen (issue 181). - ADR 0022 said production connects with DATABASE_RUNTIME_URL; no environment sets it, and deployed apps switch role per connection.
36c6b6e to
c5c0fc4
Compare
|
Thanks. The first point is settled with grants (73871ab): the worker runs as The organisation rule and the 24-hour split are in 0041 (the organisation test comes with the first real queue, #135), and the PR text now says "Tracked in #155". #100 merged as ADR 0040, where the durable job is only a condition for judges that block, so the two agree; #117 moves to 0043. |
nlundee
left a comment
There was a problem hiding this comment.
All three points are settled, and better than I asked: grants instead of a convention, with genai_worker as the only role on the job tables and a test that genai_runtime can't read, change or delete a job. Good catch on DATABASE_RUNTIME_URL in ADR 0022 too. The organisation rule and the 24-hour split read well.
One correction on numbering: 0043 is taken by #185 (code runs in agentctl), so #117 is now 0044. I've already renumbered it there.
Approving.
What and why
The knowledge database (#125) needs work that outlives a process: ingestion runs that last several nights, conversions that take hours on a GPU host, and index builds that wait for a night window. The platform has no durable queue; background work runs inside one API process under in-memory locks and heartbeats. The review on #125 asked for this part to go in on its own first.
JobQueueport in@genai/jobs, implemented with pg-boss inpackages/providers-pgboss. A job lives in thepgbossschema of the server whose state it follows and is sent in the same transaction; the worker runs jobs and the API only sends them.genai_worker, a member ofgenai_runtimewith DML onpgboss.genai_runtime, the API's role, getsUSAGEon the schema andEXECUTEon the console'sSECURITY DEFINERread functions, which requirewithPlatformAccess, and no right on the tables. A queue the API sends to grants only what sending needs. Sending dead letters back goes through the worker.platform.jobs.redrive);schedule()in Europe/Oslo time.pgbosstables have no row-level security, and the grants keep the API out of them. It also corrects its own claim that production connects withDATABASE_RUNTIME_URL: no environment sets it, and deployed apps switch role per connection. The README gets its row.Tracked in #155, built in #180. Part of digdir/kunnskapsassistenten#151.
How it was tested
Spike with pg-boss 12.38.0 in a
pgvector/pgvector:0.8.2-pg17container (tagevidence/knowledge-spikes): install with onlyCREATE, a job in a rolled-back transaction does not exist, one queued job per key, priority, three attempts and then the dead-letter queue, a killed worker's job taken over 30 s after the kill, a 10-minute job handed out once, and Node's defaultfetchgiving up after 302 s.Read pg-boss 12.38.0's source: its defaults (expiry 15 minutes, deletion 7 days after a job ends, 14 days for a job nobody takes), and what starting and sending read and write (
pgboss.version,pgboss.queue,pgboss.job_now(), an insert into the job table returning itsid).The grants are built in Run durable jobs on pg-boss in the worker, behind a JobQueue port #180, with "genai_runtime cannot read, change or delete a job", "the console reads jobs as genai_runtime, only with platform access", "the worker sends and works jobs as genai_worker" and "the migration refuses a genai_worker that is a superuser or bypasses row-level security".
Checked the Context against
main:failStaleRunsandstaleRunHoursinapps/api/src/ingestion.ts,background-lease.tsandleader-lease.ts. Checked that nothing ininfra/or.github/setsDATABASE_RUNTIME_URL.npx prettier --check docs/adr.npm run checkpasses (docs only)Checklist
packages/providers-*docs/architecture/modulesare updated if a contract, boundary or module changed