Skip to content

docs(case-study): auto-replicate Postgres → XERJ (CDC) + hybrid search (daily.dev) - #61

Merged
xerj-org merged 2 commits into
mainfrom
docs/daily-dev-postgres-cdc
Jul 28, 2026
Merged

docs(case-study): auto-replicate Postgres → XERJ (CDC) + hybrid search (daily.dev)#61
xerj-org merged 2 commits into
mainfrom
docs/daily-dev-postgres-cdc

Conversation

@xerj-team

Copy link
Copy Markdown
Collaborator

For Ido Shamun / daily.dev

Answers "is it possible to replicate automatically from pg?"yes, tested end-to-end.

Postgres 16 + pgvector 0.8.2 with logical replication → a CDC consumer streams every INSERT/UPDATE/DELETE into XERJ (with EmbeddingGemma vectors) → hybrid (BM25 + vector) search, moving the search workload off pg tsvector + pgvector.

Grounded in daily.dev's real schema

Modeled on the actual Post entity in dailydotdev/daily-api: title, summary, tagsStr, sourceId, engagement counters, metadataChangedAt watermark, + a pgvector column.

Proven live (real output)

  • Initial load: 8 posts streamed via the replication slot.
  • Then UPDATE upvotes + INSERT a new post + DELETE one → one sync pass reflected all three in XERJ (update, insert, delete) — no manual reindex.
  • Hybrid query "ditching the JVM search stack for something cheaper" ranks "Why we moved off Elasticsearch" via fused BM25+vector, where vector-alone drifts to an off-topic post and lexical-alone misses the synonyms — the query pg's tsvector+pgvector can't do in one ranked result.

Contents

  • setup.sql — daily.dev-modeled schema + change-watermark trigger + logical replication slot.
  • cdc_sync.py — the CDC consumer (slot → embed → XERJ bulk upsert/delete).
  • README.md — step-by-step runnable guide, production-CDC options (test_decoding / pgoutput / wal2json / Debezium / watermark), scaling notes, and an honest pg-vs-XERJ comparison + limitations.

Docs/examples only — no engine code.

🤖 Generated with Claude Code

Xerj Squad A added 2 commits July 27, 2026 20:31
…ch (daily.dev)

For Ido Shamun (daily.dev) — "is it possible to replicate automatically from pg?".
End-to-end tested: Postgres 16 + pgvector 0.8.2 with logical replication, a CDC
consumer that streams every INSERT/UPDATE/DELETE into XERJ with EmbeddingGemma
vectors, then hybrid (BM25 + vector) search.

Grounded in daily.dev's real Post entity (dailydotdev/daily-api): title, summary,
tagsStr, sourceId, views/upvotes/comments/score/trending, metadataChangedAt
watermark + a pgvector embedding column.

Proven live: initial load of 8 posts, then UPDATE (upvotes) + INSERT (new post) +
DELETE flowed pg->XERJ in one sync pass (slot as change-signal; consumer reads
current row, embeds, upserts/deletes by id -> convergent state). Hybrid query
"ditching the JVM search stack for something cheaper" ranks the migration post via
fused BM25+vector where vector-alone drifts and lexical-alone misses synonyms --
the thing pg tsvector+pgvector can't do in one ranked query.

Includes runnable setup.sql (schema + trigger + logical slot) and cdc_sync.py
(consumer), a production-CDC options table (test_decoding/pgoutput/wal2json/
Debezium/watermark), scaling notes, and an honest pg-vs-XERJ comparison + limits.

Co-Authored-By: Xerj Squad A <noreply@xerj.org>
…reaming, LSN) — rc.6

Addresses the three limitations flagged in the daily.dev CDC case study, all
implemented and proven live:

1. RRF hybrid — turned out to ALREADY exist first-class (`hybrid` + `fusion:rrf`,
   with weights + `linear`); the earlier "not yet" was MY request-syntax error.
   Corrected the docs to the real shape; verified live (rrf_score ~1/(60+rank)):
   fuses BM25+vector with no manual score normalization.

2. Push-streaming CDC — cdc_stream.py uses psycopg2
   LogicalReplicationConnection.consume_stream() (true push, blocks on WAL, no
   polling). Proven: an INSERT in pg appears in XERJ within seconds.

3. LSN checkpointing (exactly-once-convergent) — send_feedback(flush_lsn=...) after
   each durable apply advances confirmed_flush_lsn. Proven: stop consumer -> change
   data while down (UPDATE p1, INSERT p11) -> XERJ stale -> restart resumes from the
   last checkpoint and applies EXACTLY the missed changes, no loss, no re-sync.

Rewrote the honest-limitations section (embedding-model quality, RRF k/weight
tuning, no learned fusion, backfill cost) and moved the three resolved items to a
"Resolved" list. Complements the rc.6 knn+aggregations feature (PR #60).

Co-Authored-By: Xerj Squad A <noreply@xerj.org>
@xerj-org
xerj-org merged commit 2f3f027 into main Jul 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants