> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kair.is/llms.txt
> Use this file to discover all available pages before exploring further.

# Databases & Storage

> Where data lives, why each store was chosen, and what it holds.

## PostgreSQL — the source of truth

Everything structured lives here: users, workspaces, sessions, transcripts, jobs, summaries, speaker identities. If you need to understand "what happened in session X", the answer is in Postgres.

**Key tables:**

| Table                             | What it stores                                                      |
| --------------------------------- | ------------------------------------------------------------------- |
| `users`                           | Accounts, roles, workspace memberships                              |
| `workspaces`                      | Workspace definitions and settings                                  |
| `sessions`                        | Each recording session — title, status, timestamps                  |
| `audio_chunks`                    | Individual uploaded audio pieces                                    |
| `audios`                          | Stitched full-session audio files                                   |
| `transcription_jobs`              | Job records tracking transcription progress                         |
| `transcriptions`                  | The actual transcript text, per chunk and per full session          |
| `summarisations`                  | AI-generated session summaries                                      |
| `speakers` / `speaker_identities` | Diarisation labels and voiceprint references                        |
| `app_config`                      | Runtime admin overrides (transcription model, HiRAG settings, etc.) |

**Why Postgres?** Relational integrity matters here — a transcript belongs to a job, a job belongs to a session, a session belongs to a workspace. Foreign keys and transactions prevent orphaned records.

***

## Redis — queues & real-time

Redis serves two roles:

### 1. Job queue

When a chunk is uploaded, the API pushes a job onto a Redis list. The transcription worker pops from that list and processes the job. This decouples the HTTP request (fast, returns immediately) from the actual transcription work (slow, runs in the background).

```
API  →  RPUSH transcription_jobs:{job_id}  →  Worker polls / BLPOP
```

### 2. WebSocket pub/sub & recording status

Live session state (is a recording active? who's recording?) is stored in Redis with a TTL. The web app subscribes via WebSocket; the API publishes status changes so the UI updates without polling.

```
Redis key: session_recording_status:{session_id}
TTL: 1 hour (auto-clears stale locks)
```

**Why Redis?** Low-latency reads/writes, built-in pub/sub, TTL for ephemeral state. Postgres is too heavy for high-frequency job polling.

***

## Neo4j — knowledge graph (HiRAG)

After a session is transcribed and summarised, the HiRAG worker indexes it into a Neo4j graph. Entities (people, topics, concepts) become nodes; relationships between them become edges.

This powers the "ask a question across all sessions" feature — instead of searching raw text, the moderator queries a structured graph of what was discussed and by whom.

**Why Neo4j?** Graph traversal queries ("find all concerns related to entity X across sessions Y and Z") are natural in Cypher but painful in SQL.

<Note>
  Neo4j is only populated after HiRAG indexing completes. A session must be transcribed and summarised first. If HiRAG is disabled in config, this store is unused.
</Note>

***

## S3 — audio file storage

Raw audio (chunks and stitched files) is stored in S3-compatible object storage. The API writes files here after upload; the transcription worker reads from here to run inference.

In local development, the API falls back to local disk storage when no S3 credentials are configured.

**Why S3?** Audio files are large and binary — not suited for a relational DB. S3 scales cheaply, and the transcription worker can stream files directly without going through the API.
