docs: Firehose, Follow-Lexicon und Phase 10
architecture.md zeigt den subscribeRepos-Pfad im Diagramm (er stand zuerst irrtümlich am Pfeil zum externen Relay) und erklärt, warum es Push *und* Firehose gibt: der eine ist schnell, der andere verlässlich. Dazu, wo die Spec-Treue endet — die Frame-Hülle ist konform, die Blöcke darin nicht. deployment.md bekommt einen Firehose-Abschnitt: Transaktionsgarantie, warum die seq lückenfrei ist und was der globale Advisory-Lock an Durchsatz kostet, Cursor-Semantik, WebSocket-Upgrade im Proxy, und die fehlende Retention für firehose_events. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
This commit is contained in:
co-authored by
Claude Opus 5
parent
6fbea4fe6f
commit
b7ce114677
+39
-17
@@ -25,7 +25,8 @@ auf welchem Weg kommt ein Post vom Client bis in die Timeline zurück.
|
||||
│ /healthz │ │ GET /api/post|thread/*uri │
|
||||
│ /.well-known/did.json ──────────┼───┼─▶ Schlüssel für 🔒 │
|
||||
│ │ │ GET /api/notifications… 🔒 │
|
||||
│ │ │ GET /api/followers|following│
|
||||
│ /xrpc/…sync.subscribeRepos ─────┼───┼─▶ pds_firehose.rs (WS) │
|
||||
│ (WebSocket, seq-Cursor) │ │ GET /api/followers|following│
|
||||
│ │ │ GET /healthz │
|
||||
│ at-lexicon Validierung (160) │ │ │
|
||||
│ at-repo/at-mst MST + Commit │ │ indexer.rs Upserts │
|
||||
@@ -41,13 +42,13 @@ auf welchem Weg kommt ein Post vom Client bis in die Timeline zurück.
|
||||
┌───────────┐ ┌────────┐ ┌─────────────────┐ │
|
||||
│ Postgres │ │ MinIO │ │ Postgres │ │
|
||||
│ pds :5434 │ │ :9100 │ │ appview :5435 │ │
|
||||
└───────────┘ └────────┘ └─────────────────┘ │
|
||||
│ │
|
||||
│ (heute: kein eigener Firehose-Ausgang) │
|
||||
▼ │
|
||||
│ +firehose │ └────────┘ │ +cursor │ │
|
||||
│ _events │ └─────────────────┘ │
|
||||
└───────────┘ │
|
||||
│
|
||||
┌──────────────────────────────────────────┐ │
|
||||
│ Jetstream-Relay (extern, WebSocket) │──────────────────┘
|
||||
│ JETSTREAM_URL │ at-firehose
|
||||
│ JETSTREAM_URL — kennt diese PDS nicht │ at-firehose
|
||||
└──────────────────────────────────────────┘ JetstreamConsumer
|
||||
```
|
||||
|
||||
@@ -56,27 +57,46 @@ entsprechen. Die AppView verifiziert die ES256-Signatur mit dem öffentlichen
|
||||
Schlüssel, den die PDS in ihrem DID-Dokument veröffentlicht — `PDS_JWT_SECRET`
|
||||
verlässt die PDS nie. Details in [`deployment.md`](deployment.md), Abschnitt 6.
|
||||
|
||||
Zwei Wege führen in die AppView, und das ist Absicht:
|
||||
Drei Wege führen in die AppView, und das ist Absicht:
|
||||
|
||||
1. **Direkter Push (schnell, lokal).** Jeder erfolgreiche Commit auf der PDS
|
||||
wird per `POST /internal/ingest-commit` an die AppView geschoben
|
||||
(`crates/pds-server/src/appview_push.rs`). Best effort, 5 s Timeout, blockiert
|
||||
den Record-Write nie. Damit sieht der Nutzer seinen eigenen Post sofort.
|
||||
2. **Jetstream (global, verzögert).** `at-firehose::JetstreamConsumer` hängt an
|
||||
2. **PDS-Firehose (lokal, garantiert).** Die PDS führt in derselben
|
||||
Transaktion wie den Commit ein Event in `firehose_events` und liefert es
|
||||
über `com.atproto.sync.subscribeRepos` als WebSocket aus
|
||||
(`crates/pds-server/src/firehose.rs`). Die AppView konsumiert das mit
|
||||
persistiertem Cursor (`crates/appview/src/pds_firehose.rs`).
|
||||
3. **Jetstream (global, verzögert).** `at-firehose::JetstreamConsumer` hängt an
|
||||
einem externen Jetstream-Relay und liefert alles, was in den konfigurierten
|
||||
Collections weltweit passiert.
|
||||
|
||||
Wichtig für das Verständnis der Topologie: **die eigene PDS speist den
|
||||
Jetstream nicht.** Es gibt keinen `com.atproto.sync.subscribeRepos`-Endpoint im
|
||||
PDS-Router. Der Firehose-Weg ist ein reiner Konsum-Pfad für fremde Repos; die
|
||||
eigenen Records erreichen die AppView ausschließlich über den Push aus
|
||||
Punkt 1 (noch offen).
|
||||
Warum 1 **und** 2: Der Push ist der schnelle Weg, der Firehose der
|
||||
verlässliche. Ein verlorener Push (AppView kurz weg, Netzwerkfehler) war
|
||||
früher endgültig — der öffentliche Jetstream kennt diese PDS nicht, also
|
||||
wäre der Post nie angekommen. Jetzt holt der Cursor-Replay ihn nach.
|
||||
Dass beide Wege denselben Commit liefern, ist unkritisch: die Indexer-Pfade
|
||||
sind Upserts, und der Dedupe-Index der Notifications fängt den Rest.
|
||||
|
||||
Wichtig für das Verständnis der Topologie bleibt: **die eigene PDS speist den
|
||||
*öffentlichen* Jetstream nicht.** Weg 2 ist ein lokaler Firehose zwischen den
|
||||
eigenen zwei Diensten; ein fremder Relay erfährt von dieser PDS weiterhin
|
||||
nichts.
|
||||
|
||||
Zur Spec-Treue: Die Frame-Hülle ist konformes DAG-CBOR mit Tag-42-CID-Links.
|
||||
Die Blöcke *darin* tragen die Konvention dieses Codebases — CIDs innerhalb von
|
||||
Commit-Blöcken sind Strings, nicht Links (`at-repo/src/commit.rs`). Ein
|
||||
fremder atproto-Consumer kann die Frames also lesen, scheitert aber beim
|
||||
Validieren der Blockinhalte. Das zu ändern hieße, die Blockkodierung zu
|
||||
ändern, und damit ändern sich sämtliche CIDs inklusive der
|
||||
`did:plc:`-Ableitung — eine eigene, bewusste Migration.
|
||||
|
||||
## Crates
|
||||
|
||||
| Crate | Typ | Aufgabe |
|
||||
|---|---|---|
|
||||
| `at-lexicon` | lib | Lexicon-Schemas laden (`Lex::from_json`) und Records validieren. `LexRegistry` in der PDS kennt `app.twi.post` (160 Zeichen), `app.bsky.feed.like`, `app.bsky.feed.repost`, `app.bsky.actor.profile` — alle vier per `include_str!` einkompiliert |
|
||||
| `at-lexicon` | lib | Lexicon-Schemas laden (`Lex::from_json`) und Records validieren. `LexRegistry` in der PDS kennt `app.twi.post` (160 Zeichen), `app.bsky.feed.like`, `app.bsky.feed.repost`, `app.bsky.graph.follow`, `app.bsky.actor.profile` — alle per `include_str!` einkompiliert |
|
||||
| `at-crypto` | lib | secp256k1/P-256-Keypairs, DAG-CBOR-CIDs, multibase/base58btc, JWT (`issue_jwt` / `verify_jwt`), PLC-Operationen inkl. `did_plc_from_op` |
|
||||
| `at-identity` | lib | Handle- und DID-Auflösung. Drei Resolver hinter dem Trait `DidHandleResolver`: `PlcClient` (PLC-Directory), `WebResolver` (`.well-known/did.json`), `PdsHandleResolver` (fragt die lokale PDS) |
|
||||
| `at-mst` | lib | Merkle-Search-Tree: Knoten, `split_around`, `wrap_with_split`, spec-konformes `encode_key` |
|
||||
@@ -164,9 +184,11 @@ Alle Schreibpfade sind Upserts, das Replay nach einem Reconnect ist damit
|
||||
unschädlich. Fehlerhafte Events rücken den Cursor **nicht** vor.
|
||||
|
||||
`ingest.rs` bedient denselben Indexer über HTTP, mit den Aktionen
|
||||
`create` / `delete`; für `app.bsky.graph.follow`-Deletes braucht der Aufrufer
|
||||
`subject_did` im Body, weil der Record-Wert bei Deletes nicht garantiert
|
||||
mitkommt.
|
||||
`create` / `delete`. Für `app.bsky.graph.follow`-Deletes gibt es zwei Wege:
|
||||
der Push schickt `subject_did` im Body mit, der Firehose kennt nur `did` +
|
||||
`rkey` — deshalb speichert `follows` seit Migration 0011 den rkey des
|
||||
Follow-Records und löst darüber auf. Ohne den rkey war ein Unfollow über den
|
||||
Firehose nicht anwendbar und hing allein am Push.
|
||||
|
||||
## Datenbanken und Tabellen
|
||||
|
||||
|
||||
Reference in New Issue
Block a user