Skip to main content

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​

  1. ACID Transactions: Full ACID compliance with serializable isolation
  2. Multi-Model: Key-value core with document/graph layers
  3. Performance: Sub-10ms latency, millions of operations/sec
  4. Scalability: Distributed architecture, horizontal scaling
  5. Consistency: Strong consistency guarantees
  6. Fault Tolerance: Self-healing, automatic replication
  7. 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​

OperationTargetFoundationDB Capability
Session Load< 50ms5-10ms typical
File Save< 100ms10-20ms typical
Message Append< 20ms5-10ms typical
Concurrent Sessions100+Millions supported
Transaction Throughput10k/sec100k+/sec capable

References​