feat(journal): durable Fedify message queue over pg-boss
Task group 1 of federation-hardening. Fedify was configured with `InProcessMessageQueue`, so every queued outbound delivery, its pending retry state, and inbox processing task was lost on a container restart (routine here: deploys, OOM history) — directly contradicting the social-federation spec's promise that fan-out survives a deploy. - `federation-queue.server.ts`: `PgBossMessageQueue` implementing Fedify's `MessageQueue` over the pg-boss instance the journal already runs, mirroring the `PostgresKvStore` adapter. `nativeRetrial = false` keeps Fedify the retry-policy owner; pg-boss supplies durability + delayed jobs (delay → whole-second `startAfter`, `retryLimit: 0`). - Swap it in for `InProcessMessageQueue` in `federation.server.ts`; document the two intentional queueing layers (our fan-out jobs feed Fedify; Fedify's sends now durable underneath). - `server.ts`: create the durable queue at startup when federation is on. - Broaden the structural `BossLike` in `boss.server.ts` with the work/offWork/createQueue/getQueue methods the adapter needs. - Tests: enqueue maps retry/delay correctly, consume roundtrip, restart durability (fresh listener drains a prior instance's backlog), abort stops the worker, depth reports ready vs delayed. Verified: journal typecheck + lint clean, 7/7 new unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1ea2559246
commit
b8fc8fadff
6 changed files with 252 additions and 12 deletions
|
|
@ -27,7 +27,7 @@
|
|||
// exactly the "acknowledge and drop" the spec wants; the same applies
|
||||
// to wrapped types we inspect and ignore (e.g. Undo(Like)).
|
||||
import { configure, getConsoleSink } from "@logtape/logtape";
|
||||
import { createFederation, InProcessMessageQueue, type Federation } from "@fedify/fedify";
|
||||
import { createFederation, type Federation } from "@fedify/fedify";
|
||||
import { Accept, Follow, Note, Person, PropertyValue, Reject, Undo } from "@fedify/fedify/vocab";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { activities, users } from "@trails-cool/db/schema/journal";
|
||||
|
|
@ -35,6 +35,7 @@ import { getDb } from "./db.ts";
|
|||
import { getOrigin } from "./config.server.ts";
|
||||
import { localActorIri } from "./actor-iri.ts";
|
||||
import { PostgresKvStore } from "./federation-kv.server.ts";
|
||||
import { PgBossMessageQueue } from "./federation-queue.server.ts";
|
||||
import { ensureUserKeypair, loadUserKeypair } from "./federation-keys.server.ts";
|
||||
import { activityToCreate, activityToNote } from "./federation-objects.server.ts";
|
||||
import {
|
||||
|
|
@ -148,12 +149,17 @@ function buildFederation(): Federation<void> {
|
|||
// (e.g. the Accept we push back after a Follow) happen asynchronously
|
||||
// with Fedify's own retry policy, instead of inside the inbound HTTP
|
||||
// request — Mastodon gives deliveries a 10s read timeout, and a slow
|
||||
// remote must never make us blow it. In-process is acceptable for
|
||||
// the single-process journal: queued work is lost on restart, but
|
||||
// every message type we enqueue is recoverable (remotes retry
|
||||
// Follows; our own pg-boss jobs own activity delivery). Revisit with
|
||||
// a Postgres-backed MessageQueue if that stops being true.
|
||||
queue: new InProcessMessageQueue(),
|
||||
// remote must never make us blow it.
|
||||
//
|
||||
// Two queueing layers coexist by design: our fan-out jobs
|
||||
// (federation-delivery.server.ts enqueues one deliverActivity pg-boss
|
||||
// job per follower) feed Fedify, and Fedify's own send/retry
|
||||
// scheduling now runs on THIS durable pg-boss-backed queue instead of
|
||||
// in-process. Before, in-process meant a deploy or crash dropped every
|
||||
// queued delivery and pending retry (spec: fan-out survives a deploy);
|
||||
// PgBossMessageQueue closes that. Collapsing the two layers into one
|
||||
// is a refactor with no user-visible payoff, so they stay separate.
|
||||
queue: new PgBossMessageQueue(),
|
||||
// Started explicitly below — keeps vitest runs (which build this
|
||||
// instance via handleFederationRequest) free of dangling timers.
|
||||
manuallyStartQueue: true,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue