OOOC Fête Finder uses PostgreSQL as the primary data store for events, user sessions, rate limits, and backups. This guide covers database setup, schema initialization, and optimization.
CREATE TABLE app_event_store_columns ( key TEXT PRIMARY KEY, label TEXT NOT NULL, is_core BOOLEAN NOT NULL, is_required BOOLEAN NOT NULL, display_order INTEGER NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW());
Defines the event sheet schema with:
Core columns: title, date, location, categories, etc.
CREATE TABLE app_event_store_rows ( id TEXT PRIMARY KEY, display_order INTEGER NOT NULL, row_data JSONB NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW());
CREATE TABLE app_event_store_meta ( singleton BOOLEAN PRIMARY KEY DEFAULT TRUE, row_count INTEGER NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_by TEXT NOT NULL DEFAULT 'system', origin TEXT NOT NULL DEFAULT 'manual', checksum TEXT NOT NULL DEFAULT '');
# Override default pool size (bounded 1-10)POSTGRES_POOL_MAX=5
Recommended settings:
Development: 1 (default)
Preview: 2-3
Production: 3-5 (default: 3)
High traffic: 5-10
Keep pool size low on serverless platforms. Each function instance maintains its own pool. With 10 concurrent functions and pool size 5, you’d have 50 database connections.
-- Speed up KV lookups by prefixCREATE INDEX idx_kv_key_prefix ON app_kv_store(key text_pattern_ops);-- Speed up event row orderingCREATE INDEX idx_event_rows_display_order ON app_event_store_rows(display_order);-- Speed up JSONB queries on common fieldsCREATE INDEX idx_event_rows_title ON app_event_store_rows USING gin ((row_data->'title'));CREATE INDEX idx_event_rows_date ON app_event_store_rows USING gin ((row_data->'date'));CREATE INDEX idx_event_rows_categories ON app_event_store_rows USING gin ((row_data->'categories'));
Add indexes only if you observe slow queries. Start with the default schema and profile before optimizing.
// Efficient: Use JSONB operatorsconst events = await sql` SELECT row_data->>'title' as title, row_data->>'date' as date FROM app_event_store_rows WHERE row_data->>'date' >= ${startDate} ORDER BY display_order`;// Avoid: Deserializing all rows in applicationconst allRows = await sql`SELECT * FROM app_event_store_rows`;const filtered = allRows.filter(r => parseDate(r.row_data.date) >= startDate);
await sql.begin(async (tx) => { await tx`DELETE FROM app_event_store_rows`; await tx`DELETE FROM app_event_store_columns`; // Insert new data for (const column of columns) { await tx`INSERT INTO app_event_store_columns ...`; } for (const row of rows) { await tx`INSERT INTO app_event_store_rows ...`; } await tx`UPDATE app_event_store_meta SET row_count = ${rows.length}`;});