Quickstart¶
Get CIUF running against your PostgreSQL database in 15 minutes. By the end you'll have a JOIN query returning cached results with sub-millisecond latency, and you'll see the before/after numbers yourself.
1. Install (1 min)¶
Requires Python 3.11+. The [postgres] extra pulls in sqlalchemy[asyncio] and psycopg2-binary. For SQLite only (dev/testing): pip install ciuf.
Verify:
2. Connect (2 min)¶
On construction, CIUF connects to your database and discovers the schema: one TableNode per table, no data loaded yet. The connection string is a standard SQLAlchemy URL.
Connection string formats
3. Run your first cached JOIN (5 min)¶
Use from_sql to parse any SQL SELECT and get a result backed by the in-memory DAG:
from ciuf import Engine, from_sql
engine = Engine("postgresql://user:password@localhost:5432/mydb")
result = from_sql(engine, """
SELECT
orders.id,
orders.amount,
customers.name,
customers.plan
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE customers.plan = 'pro'
ORDER BY orders.amount DESC
LIMIT 100
""")
# First call: loads data from PostgreSQL and builds the DAG
df = result.query()
print(df.head())
The first call executes the full query against PostgreSQL and caches the result. Every subsequent call with the same query pattern returns from memory. No database roundtrip.
4. Measure the improvement (5 min)¶
Here's how to time the difference yourself:
import time
from ciuf import Engine, from_sql
engine = Engine("postgresql://user:password@localhost:5432/mydb")
result = from_sql(engine, """
SELECT
date_trunc('day', orders.created_at) AS day,
SUM(orders.amount) AS revenue,
COUNT(*) AS order_count,
customers.plan
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE orders.status = 'completed'
GROUP BY date_trunc('day', orders.created_at), customers.plan
ORDER BY day DESC
LIMIT 30
""")
# Cold read — loads from PostgreSQL
t0 = time.perf_counter()
df = result.query()
cold_ms = (time.perf_counter() - t0) * 1000
print(f"Cold read: {cold_ms:.1f} ms ({len(df)} rows)")
# Hot read — returns from in-process cache
t0 = time.perf_counter()
df2 = result.query()
hot_ms = (time.perf_counter() - t0) * 1000
print(f"Hot read: {hot_ms:.3f} ms")
print(f"Speedup: {cold_ms / hot_ms:.0f}×")
Typical output on a JOIN query over 100k rows:
The hot read is sub-millisecond because CIUF returns the cached DataFrame directly. No network, no parsing, no serialization.
5. Keep the cache in sync (2 min)¶
CIUF does not poll the database for changes. You notify it of every write:
# After INSERT
engine.on_insert("orders", {
"id": 12345,
"amount": 99.0,
"customer_id": 42,
"status": "completed",
"created_at": "2026-05-09T12:00:00",
})
# After UPDATE
engine.on_update("customers",
new={"id": 42, "plan": "pro", "name": "Acme Corp"},
old={"id": 42, "plan": "free", "name": "Acme Corp"},
)
# After DELETE
engine.on_delete("orders", {"id": 12345})
CIUF propagates only the delta. No full table reload. Downstream joins and aggregations update incrementally.
Configuration reference¶
engine = Engine(
"postgresql://user:pass@localhost/mydb",
max_memory_mb=512, # LRU eviction when cache exceeds this (default: no limit)
ttl_seconds=3600, # Evict results older than this (default: no expiry)
)