---
title: "Fabriq"
tagline: "Data fabric"
language: "Go"
license: "Apache-2.0"
since: "2026"
canonical: "https://xraph.com/work/fabriq"
---

# Fabriq

One write path for applications that outgrew a single database. A command commits state, a versioned event and an outbox row in the same Postgres transaction; a leader-elected relay publishes to Redis Streams; the graph (FalkorDB) and search (Elasticsearch) projections are rebuilt from that log rather than written to directly. Tenancy is structural, using row level security, a graph per tenant and index routing, rather than a WHERE clause you can forget.

## Install

```bash
go get github.com/xraph/fabriq
```

## What it is

Fabriq is a data fabric for Go: one transactional write path that fans out to relational, time-series, vector, graph and search engines. It exists because an application that outgrows a single database usually grows a second write in the same handler, and that second write is where silent divergence starts.

## Three invariants

Everything else in the design follows from these, and each is enforced structurally rather than by convention.

- **Every write emits exactly one versioned event.** A command runs in a Postgres transaction that applies state, appends the event and inserts an outbox row. A leader-elected relay, woken by LISTEN/NOTIFY, publishes it to Redis Streams in order.
- **Every access is tenant-scoped.** Tenant rides on context.Context and is stamped into each engine the way that engine can enforce: Postgres SET LOCAL plus row-level security, a FalkorDB graph per tenant, Elasticsearch index routing, Redis key prefixes. A pre-query hook is a loud backstop, not the mechanism.
- **Projections are rebuildable from Postgres.** The knowledge graph and the search index are derived. Nothing writes to them except an applier reading the log, so both can be dropped and replayed.

## What is implemented

All of the following has integration coverage:

- **Command plane and outbox**: registry-driven commands, optimistic concurrency, atomic batches.
- **Postgres as source of truth**: RLS verified as a non-superuser, Timescale hypertables for bulk telemetry, pgvector with HNSW for similarity, and PostGIS for geometry with GiST-accelerated Within queries (SRID 4326 resolves to true metres).
- **Dynamic entities**: entities defined at runtime from a schema descriptor rather than a Go struct, with fabriq-managed DDL, map-native reads and writes over real typed columns, and the full projection pipeline. It is a fenced, opt-in lane so the migrations-as-authority discipline stays intact for everything else.
- **Redis Streams fan-out**: consumer groups with XAUTOCLAIM recovery, plus a subscription hub with delta conflation, SSE and Last-Event-ID resume.
- **Live queries**: a filter/sort/limit subscription returns a snapshot then exact enter, leave, move and update deltas. The in-engine window stays an exact prefix of the Postgres-ordered result through a cushion and keyset boundary refill, so top-N is exact at all times.
- **Graph projection**: an openCypher dialect behind a conformance suite that acts as the engine-swap gate, a batched traverse-and-hydrate, and blue-green rebuilds verified to produce an identical graph.
- **Search projection**: version-gated bulk writes, lazy per-tenant index and alias provisioning, and atomic alias-swap rebuilds.
- **Reconciler**: per-aggregate drift detection between Postgres and the projections, classified as missing, stale or zombie.

## Operating it

cmd/fabriq is the whole fabric in one binary. serve runs the worker (outbox relay, projection consumers, reconciler and document plane), and migrate, rebuild, reconcile and inspect are the operator commands.

> The property worth the complexity is not exactly-once delivery. It is that dropping a projection entirely is a routine operation rather than an incident.

---

*Unpublished draft. Not peer reviewed and not submitted to a venue.*


## Anatomy

- **Command plane**: One transaction and an outbox; every write emits exactly one versioned event.
- **Postgres**: The source of truth. Everything else is a projection.
- **Fan-out**: Redis Streams carrying events to graph, search and live queries.
- **Reconciler**: Re-projects the affected window when an observation arrives late.

## Constraints and trade-offs

- (decision) No dual writes. A second write path is a deferred incident.
- (constraint) Every access is tenant-scoped at the store layer. A query cannot cross a boundary by omission.
- (trade-off) Write latency is bounded by the slowest engine in the set. You delete the drift dashboard in exchange.
