No Server | No SQL | No Config
pip install andal and forget about your analytics infrastructure.
SQLite for events — embedded, columnar, zero ops.
Andal is an embedded event store for Python. Track events, query them, run funnels and aggregations — all from a simple Python API, no SQL required.
pip install andalimport andal
# Open or create a store
store = andal.EventStore("./data")
# Track events (kwargs become properties)
store.track("page_view", user_id=123, page="/pricing")
store.track("click", user_id=123, button="signup")
store.track("purchase", user_id=123, amount=99.99)
# Query events
results = store.filter(event_type="purchase", user_id=123)
for event in results:
print(event["timestamp"], event["user_id"], event["properties"])
# Close store
store.close()Andal is a fast, embedded event store built for analytics. Store millions of events and query them efficiently without setting up a database server.
|
Forget servers, docker, and config. Open a file path and start tracking — works in notebooks, Flask apps, Lambda functions, anywhere Python runs. |
from andal import EventStore
# This is the entire setup.
store = EventStore("./my_app")
store.track("signup", user_id=1)
store.track("purchase", user_id=1, amount=49.99)
store.track("churn", user_id=2)
# Data is on disk. Nothing else required. |
|
Funnels, aggregations, and filters in one line of Python. No GROUP BY, JOINs, or headaches. |
# Conversion funnel
store.funnel(["page_view", "signup", "purchase"])
# page_view → 10,000 users
# signup → 1,200 (12%)
# purchase → 240 (2.4%)
# What's happening right now?
store.count_by("event_type", since="24h")
# {"page_view": 8432, "signup": 91}
# Unique users this week
store.unique("user_id", since="7d") # 4,891 |
|
Every event is timestamped and immutable. Ask questions a regular database can't answer and get it answered fast: full user histories, pre-crash sequences, pattern analysis. |
# Everything a user ever did
store.filter(user_id=123)
# All errors in the last hour
store.filter(event_type="error", since="1h")
# What happened before this crash?
store.filter(user_id=123, end_time=crash_timestamp)
# Full history. Nothing deleted. Ever. |
|
Events hit a write-ahead log before anything else. If your process dies, data is recovered automatically on next open. |
store.track("payment_confirmed", order_id="ORD-123", amount=99.99)
# Process crashes here.
# On restart:
store = EventStore("./my_app")
# ✓ payment_confirmed is recovered
# ✓ No data loss
# ✓ No manual recovery steps |
import andal
# Open or create a store
store = andal.EventStore("./my_events")
# Use as context manager (recommended)
with andal.EventStore("./my_events") as store:
store.track("event", user_id=1)
# Automatically closed and flushed# Basic event tracking
store.track("page_view", user_id=123)
# With timestamp (milliseconds since epoch)
import time
now_ms = int(time.time() * 1000)
store.track("click", user_id=123, timestamp=now_ms)
# With properties (kwargs become JSON properties)
store.track(
"purchase",
user_id=123,
timestamp=now_ms,
amount=99.99,
currency="USD",
product_id="prod_123"
)# Filter by event type
purchases = store.filter(event_type="purchase")
# Filter by user
user_events = store.filter(user_id=123)
# Filter by time range
recent = store.filter(
start_time=day_ago_ms,
end_time=now_ms
)
# Combine filters
recent_purchases = store.filter(
event_type="purchase",
user_id=123,
start_time=day_ago_ms
)
# Results are lists of event dictionaries
for event in recent_purchases:
print(event["event_type"]) # "purchase"
print(event["user_id"]) # 123
print(event["timestamp"]) # 1710000000000
print(event["properties"]) # {"amount": 99.99, ...}# Count events by dimension
event_counts = store.count_by("event_type")
# {"page_view": 5234, "click": 892, "purchase": 127}
user_counts = store.count_by("user_id", event_type="purchase")
# {123: 5, 456: 3, 789: 12}
# Count unique values
unique_users = store.unique("user_id")
# 4523
unique_purchasers = store.unique("user_id", event_type="purchase")
# 89
# Get all event type counts
all_counts = store.event_counts()
# {"page_view": 5234, "click": 892, "purchase": 127}# Track conversion through multiple steps
funnel = store.funnel(
steps=["page_view", "add_to_cart", "purchase"],
within=3_600_000 # Users must complete within 1 hour
)
# Returns conversion at each step
# [
# {"step": "page_view", "users": 1000, "conversion_rate": 1.0},
# {"step": "add_to_cart", "users": 120, "conversion_rate": 0.12},
# {"step": "purchase", "users": 24, "conversion_rate": 0.024}
# ]
# Without time window (users can take any amount of time)
funnel = store.funnel(steps=["signup", "first_purchase"])# Get earliest event (optimized with metadata)
first = store.first()
first_purchase = store.first(event_type="purchase")
first_user_event = store.first(user_id=123)
# Get most recent event
last = store.last()
last_error = store.last(event_type="error")
# These use segment metadata for O(segments) performance
# instead of O(events) when no filters are specified# Get total event count
count = store.size()
# 15234
# Force flush to disk (usually not needed - auto-flushes at 10K events)
store.flush()
# Close store (flushes pending writes)
store.close()Note: Comprehensive benchmarks pending. Current performance characteristics:
Write Performance:
- WAL-buffered writes: ~100K+ events/sec (in-memory)
- Auto-flush at 10K events or 4MB threshold
- Segment write: <100ms for 10K events
Query Performance:
- Linear scan filtering (no indexes yet)
- Partition pruning via time-range metadata
- Lazy segment loading reduces memory footprint
Memory:
- ~28 bytes/event in-memory (columnar arrays)
- Segments unloaded after query to conserve memory
Full benchmarks vs SQLite/DuckDB coming soon.
┌─────────────────────────────────────────────────┐
│ Python API (andal.EventStore) │
└───────────────────┬─────────────────────────────┘
│
┌───────────────────▼─────────────────────────────┐
│ C Core │
│ ├── WAL (Write-Ahead Log) │
│ ├── EventBlock (Columnar In-Memory) │
│ ├── Segments (Immutable Disk Files) │
│ ├── InvertedIndex (Fast Lookups) │
│ └── PartitionIndex (Time Pruning) │
└─────────────────────────────────────────────────┘
│
┌───────────────────▼─────────────────────────────┐
│ Disk Storage │
│ ├── wal.log (durability) │
│ ├── seg_00001.dat (columnar data) │
│ ├── seg_00002.dat │
│ └── event_types.txt │
└─────────────────────────────────────────────────┘
Key Design Principles:
- Columnar Storage: Each field in separate arrays for cache-efficient scans
- Immutable Segments: Write-once files, easy to reason about
- Time Partitioning: Binary search prunes irrelevant data
- Crash Recovery: WAL ensures no data loss
- Product Analytics: Track user behavior, conversion funnels (Mixpanel alternative)
- Application Monitoring: Errors, performance metrics, audit logs
- Business Intelligence: Sales events, revenue tracking
- IoT & Sensors: Time-series sensor data
- Security: Access logs, anomaly detection
Current Status: Alpha (Active Development)
Core C Engine:
- ✅ Core columnar storage (EventBlock)
- ✅ Write-ahead log (WAL) with durability
- ✅ Segment persistence to disk
- ✅ Crash recovery and segment scanning
- ✅ String dictionary encoding with persistence
- ✅ Basic filtering queries (event_type, user_id, time range)
- ✅ Time-based partition index (query pruning)
- ✅ Lazy segment loading/unloading
- ⏳ Inverted indexes (planned - currently linear scan)
- ⏳ Compression (delta + LZ4) (planned)
Python API:
- ✅ Full Python bindings via C extension
- ✅ High-level
track()API with kwargs→properties - ✅ Filter queries with result parsing
- ✅ Aggregations:
count_by(),unique(),event_counts() - ✅ Funnel analysis with time windows
- ✅ First/last queries with metadata optimization
- ⏳ Python packaging (planned)
See ARCHITECTURE.md for detailed design docs.
- Use context managers:
with EventStore(...) as store:ensures proper cleanup - Time filters help: Include time ranges to leverage partition pruning
- Auto-flush works: Default threshold is 10K events - manual
flush()rarely needed - Properties as kwargs:
track("click", user_id=1, btn="x")is cleaner than JSON strings
Andal is in early development. Issues and PRs are welcome!
# Build and test C core
make clean
make test # Runs all C tests (wal, disk, store)
# Test Python bindings
make python # Build Python extension
python tests/test_store_python.py
# Individual test targets
make test-wal # WAL durability tests
make test-disk # Segment serialization tests
make test-store # Full integration testsTBD
Built with ⚡ for speed