ADR-004: Use FoundationDB for Persistence
Date: 2025-10-06 Status: Accepted Deciders: Development Team Tags: database, persistence, architecture
Context​
The IDE requires persistent storage for:
- Session data (multiple simultaneous sessions)
- File contents and metadata
- llm conversation history
- User settings and preferences
- editor state across sessions
We need a database that supports:
- ACID transactions
- High performance
- Multi-model data (key-value, document, graph)
- Strong consistency
- Scalable architecture
Decision​
We will use FoundationDB 7.1+ as the primary persistence layer.
Rationale​
Why FoundationDB​
- ACID Transactions: Full ACID compliance with serializable isolation
- Multi-Model: Key-value core with document/graph layers
- Performance: Sub-10ms latency, millions of operations/sec
- Scalability: Distributed architecture, horizontal scaling
- Consistency: Strong consistency guarantees
- Fault Tolerance: Self-healing, automatic replication
- Open Source: Apache 2.0 license, active community
Key Features for IDE​
- Session Isolation: Each session in separate subspace
- Atomic Updates: File saves, state changes in transactions
- Watch API: Real-time updates for collaborative features
- Conflict Resolution: Automatic handling of concurrent edits
- Backup/Restore: Point-in-time recovery
Architecture Integration​
Data Model​
/az1ai-ide/
├── sessions/
│ ├── {session-id}/
│ │ ├── metadata # Session info, timestamps
│ │ ├── editor-tabs/ # Open files, positions
│ │ ├── llm-messages/ # Conversation history
│ │ └── config/ # Session-specific settings
├── files/
│ ├── {file-path}/
│ │ ├── content # File contents
│ │ ├── metadata # Language, encoding, timestamps
│ │ └── versions/ # Version history
├── settings/
│ ├── global/ # Global preferences
│ └── workspace/ # workspace settings
└── models/
└── {model-id}/ # Model configurations
Subspace Design​
// Directory layer structure
const az1aiDirectory = new DirectoryLayer();
const sessionsSubspace = az1aiDirectory.createOrOpen(db, ['sessions']);
const filesSubspace = az1aiDirectory.createOrOpen(db, ['files']);
const settingsSubspace = az1aiDirectory.createOrOpen(db, ['settings']);
Alternatives Considered​
IndexedDB​
- Pros: Built into browser, no server needed
- Cons: Limited to single browser, no multi-client sync, limited query capabilities
- Rejected: Not suitable for multi-session architecture, lacks ACID guarantees
PostgreSQL​
- Pros: Mature, SQL support, JSON support
- Cons: Relational overhead, more complex schema, less flexible for key-value
- Rejected: Overkill for IDE needs, less performance than FoundationDB
MongoDB​
- Pros: Document model, flexible schema
- Cons: Eventual consistency (by default), larger footprint
- Rejected: Consistency guarantees not as strong as needed
Redis​
- Pros: Fast, simple, good for caching
- Cons: Limited durability options, no complex transactions
- Rejected: Not suitable as primary database for IDE state
SQLite (via WASM)​
- Pros: Embedded, SQL support, single file
- Cons: No distributed support, limited concurrency
- Rejected: Doesn't scale for multi-session architecture
Consequences​
Positive​
- Strong consistency for multi-session editing
- High performance for real-time operations
- Flexible data model (key-value, documents)
- Built-in fault tolerance and replication
- Scalable for future collaborative features
- Transaction support prevents data corruption
Negative​
- Additional service to run (FoundationDB server)
- Learning curve for FDB-specific concepts
- More complex deployment than browser-only solutions
- Requires server infrastructure (can't be fully static)
Neutral​
- Need to implement layer on top of key-value core
- Requires client library in frontend
Implementation​
Client Integration​
// FoundationDB client for browser
import { open } from '@foundationdb/foundationdb';
class FDBService {
private db: Database;
private sessionsDir: Subspace;
private filesDir: Subspace;
async initialize(): Promise<void> {
this.db = open();
this.sessionsDir = await this.db.createOrOpen(['sessions']);
this.filesDir = await this.db.createOrOpen(['files']);
}
async saveSession(sessionId: string, data: SessionData): Promise<void> {
await this.db.doTransaction(async (tr) => {
const sessionKey = this.sessionsDir.pack([sessionId, 'metadata']);
tr.set(sessionKey, JSON.stringify(data));
});
}
async loadSession(sessionId: string): Promise<SessionData> {
return await this.db.doTransaction(async (tr) => {
const sessionKey = this.sessionsDir.pack([sessionId, 'metadata']);
const data = await tr.get(sessionKey);
return JSON.parse(data.toString());
});
}
async saveFile(path: string, content: string): Promise<void> {
await this.db.doTransaction(async (tr) => {
const contentKey = this.filesDir.pack([path, 'content']);
const metaKey = this.filesDir.pack([path, 'metadata']);
tr.set(contentKey, content);
tr.set(metaKey, JSON.stringify({
updatedAt: new Date(),
size: content.length
}));
});
}
}
Connection Configuration​
// Connect to local FoundationDB instance
const FDB_CONFIG = {
clusterFile: '/etc/foundationdb/fdb.cluster', // Docker mount
apiVersion: 710,
connectionOptions: {
timeout: 5000,
retryLimit: 5
}
};
Docker Integration​
# docker-compose.yml
services:
foundationdb:
image: foundationdb/foundationdb:7.1.27
container_name: az1ai-fdb
ports:
- "4500:4500"
volumes:
- fdb-data:/var/lib/foundationdb
- fdb-logs:/var/log/foundationdb
environment:
FDB_NETWORKING_MODE: host
FDB_COORDINATOR_PORT: 4500
az1ai-ide:
build: .
depends_on:
- foundationdb
environment:
FDB_CLUSTER_FILE: /etc/foundationdb/fdb.cluster
volumes:
- fdb-cluster:/etc/foundationdb:ro
volumes:
fdb-data:
fdb-logs:
fdb-cluster:
Migration Strategy​
Phase 1: Parallel Operation​
- Run FoundationDB alongside existing storage
- Write to both systems
- Read from FoundationDB, fallback to old storage
Phase 2: Migration​
- Background job to migrate existing data
- Session-by-session migration
- Verify data integrity
Phase 3: Cutover​
- Switch to FoundationDB-only reads
- Remove old storage system
- Clean up migration code
Performance Targets​
| Operation | Target | FoundationDB Capability |
|---|---|---|
| Session Load | < 50ms | 5-10ms typical |
| File Save | < 100ms | 10-20ms typical |
| Message Append | < 20ms | 5-10ms typical |
| Concurrent Sessions | 100+ | Millions supported |
| Transaction Throughput | 10k/sec | 100k+/sec capable |