Database Integrations

SQLite, PostgreSQL, MySQL, and MS SQL Server — how to configure each, Alembic migrations, connection pooling, and ChromaDB for vector storage.

Overview

Luna uses SQLAlchemy as its ORM and supports four database backends: SQLite (default, zero-config), PostgreSQL, MySQL/MariaDB, and MS SQL Server. All use the same schema and the same migration path via Alembic.

ChromaDB stores embedding vectors for semantic fact retrieval and episodic memory. It runs as a separate embedded database alongside the main relational store.

DatabaseUse casePackage
SQLiteLocal / development / single-userBuilt-in (no extra deps)
PostgreSQLProduction, Supabase, high concurrencypsycopg2-binary
MySQL / MariaDBExisting MySQL infrastructurepymysql
MS SQL ServerEnterprise / Azure SQLpyodbc
ChromaDBVector embeddings (all backends)chromadb (included)

SQLite (default)

SQLite is the default and requires no configuration. The database file is created at data/luna.db on first run.

.env — SQLite (default)
# Leave DB_URL blank — SQLite is used automatically
# DB_URL=    ← not set

# Optional: change the file path
DB_PATH=data/luna.db

# Optional: custom data directory
LUNA_DATA_DIR=/path/to/luna-data
💡
SQLite is fully supported for single-user personal deployments. For multi-user business deployments with concurrent writes, use PostgreSQL.

SQLite limitations

  • No connection pool (DB_POOL_SIZE is ignored).
  • Write concurrency is limited by SQLite's file-level locking.
  • Not suitable for multi-process deployments (Gunicorn with workers > 1).

PostgreSQL / Supabase

.env
DB_URL=postgresql+psycopg2://user:password@localhost:5432/luna

# Supabase (pooler URL — recommended for serverless):
DB_URL=postgresql+psycopg2://postgres.xxxx:password@aws-0-us-east-1.pooler.supabase.com:6543/postgres
install
pip install psycopg2-binary

PostgreSQL-specific settings

.env
DB_POOL_SIZE=10       # persistent connections in the pool
DB_MAX_OVERFLOW=20   # extra connections above pool_size under load
DB_POOL_TIMEOUT=30   # seconds to wait for a free connection
DB_POOL_RECYCLE=1800 # recycle connections every 30 min (prevents stale TCP)
DB_ECHO=false        # log every SQL statement (development only)

Supabase setup

  1. Create a project at supabase.com.
  2. Go to Project Settings → Database → Connection string → Python.
  3. Copy the connection string into DB_URL.
  4. Run alembic upgrade head to create the schema.

MySQL / MariaDB

.env
DB_URL=mysql+pymysql://user:password@localhost:3306/luna

# With charset (recommended):
DB_URL=mysql+pymysql://user:password@localhost:3306/luna?charset=utf8mb4
install
pip install pymysql
create the database (MySQL CLI)
CREATE DATABASE luna CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'luna_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON luna.* TO 'luna_user'@'localhost';
FLUSH PRIVILEGES;
📌
Always create the MySQL database with utf8mb4 charset to support emoji and full Unicode in conversation content and memory facts.

MS SQL Server / Azure SQL

.env — SQL Server
DB_URL=mssql+pyodbc://user:password@server/luna?driver=ODBC+Driver+17+for+SQL+Server
.env — Azure SQL
DB_URL=mssql+pyodbc://@myserver.database.windows.net/luna?driver=ODBC+Driver+17+for+SQL+Server&Authentication=ActiveDirectoryInteractive
install
pip install pyodbc
# Also install the ODBC driver: https://learn.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server

Create the database (T-SQL)

SQL Server Management Studio or Azure Data Studio
CREATE DATABASE luna COLLATE SQL_Latin1_General_CP1_CI_AS;
CREATE LOGIN luna_user WITH PASSWORD = 'YourPasswordHere';
USE luna;
CREATE USER luna_user FOR LOGIN luna_user;
ALTER ROLE db_owner ADD MEMBER luna_user;

Connection pool

SQLAlchemy's connection pool is configured globally. These settings apply to PostgreSQL, MySQL, and MS SQL Server (not SQLite):

Env varDefaultDescription
DB_POOL_SIZE10Persistent connections maintained at all times.
DB_MAX_OVERFLOW20Extra connections allowed above pool_size during spikes.
DB_POOL_TIMEOUT30Seconds to wait for a free connection before raising.
DB_POOL_RECYCLE1800Max connection age in seconds (prevents stale TCP drops).
DB_ECHOfalseLog every SQL statement to stdout (dev/debug only).

The total max connections = DB_POOL_SIZE + DB_MAX_OVERFLOW. For Supabase or PlanetScale with connection limits, reduce DB_POOL_SIZEto 5 and DB_MAX_OVERFLOW to 10.

Migrations (Alembic)

Luna uses Alembic for schema management. Run migrations after switching database backends or updating Luna.

terminal
# Apply all pending migrations
alembic upgrade head

# Check current revision
alembic current

# Show migration history
alembic history

# Roll back one step
alembic downgrade -1
📌
Always run alembic upgrade head after changing DB_URLto a new backend. The first run creates all tables from scratch.

Creating a migration

terminal
# After editing SQLAlchemy models in backend/models/database.py:
alembic revision --autogenerate -m "add my_new_column"
alembic upgrade head

Schema overview

Key tables in backend/models/database.py:

TableDescription
conversationsConversation sessions with title and metadata.
messagesIndividual messages (role, content, timestamps).
factsLong/short-term memory facts with category, confidence, importance.
fact_relationshipsTyped graph edges between facts (CONTRADICTS, CONFIRMS, etc.).
episodesConversation episode summaries with key fact IDs and embeddings.
personality_stateCurrent mood and style preference floats.
state_eventsHistorical state inferences for pattern learning.
tasksUser tasks with due date, priority, and completion status.
calendar_eventsScheduled events with start/end datetime.
proactive_logRecord of proactive messages sent by the scheduler.
audit_logTool call history with args, results, and conversation ID.
contradiction_notesRecords of detected fact contradictions.

ChromaDB (vectors)

ChromaDB stores embedding vectors for two purposes:

  • luna_facts — fact embeddings for semantic retrieval in MemoryManager.
  • luna_episodes — episode embeddings for long-range conversation recall in MemoryGraph.
.env
# ChromaDB storage path (relative to LUNA_DATA_DIR)
CHROMA_PATH=data/chroma    # default

# Embedding provider (used by ChromaDB ingestion)
EMBEDDING_PROVIDER=ollama  # or openai-compatible

ChromaDB runs embedded in the same process — no separate server. Data is persisted to disk at CHROMA_PATH. Deleting this directory clears all vectors (facts and episodes remain in the relational DB but lose semantic search until re-embedded).

📌
ChromaDB is independent of the relational backend — it always uses its own local persistent storage regardless of which SQL database you choose.

Backup and restore

SQLite

terminal
# Backup
cp data/luna.db data/backups/luna-$(date +%Y%m%d).db

# Restore
cp data/backups/luna-20250601.db data/luna.db

PostgreSQL

terminal
pg_dump luna > luna-backup.sql
psql luna < luna-backup.sql

ChromaDB

terminal
# ChromaDB is a directory — copy the entire folder
cp -r data/chroma data/backups/chroma-$(date +%Y%m%d)

Luna automatically creates daily backups of SQLite to data/backups/via the scheduler. For PostgreSQL and MySQL, configure your own backup strategy.