PostgreSQL Store
Production-ready PostgreSQL store using grove ORM with pgdriver.
The PostgreSQL Store is Ledger's production storage backend. It uses the grove ORM with the PostgreSQL driver, providing persistent storage with automatic migrations, multi-tenant isolation, and optimized queries for high-throughput billing operations.
Setup
import (
"github.com/xraph/grove"
"github.com/xraph/grove/drivers/pgdriver"
"github.com/xraph/ledger"
"github.com/xraph/ledger/store/postgres"
)
db, err := grove.Open(pgdriver.Open(os.Getenv("DATABASE_URL")))
if err != nil {
log.Fatal(err)
}
s := postgres.New(db)
engine := ledger.New(s,
ledger.WithMeterConfig(100, 5*time.Second),
ledger.WithEntitlementCacheTTL(30*time.Second),
)
// Start runs migrations automatically
if err := engine.Start(ctx); err != nil {
log.Fatal(err)
}
defer engine.Stop()Migrations
Migrations run automatically when engine.Start() is called via the Migrate(ctx) method. The store uses the grove orchestrator with programmatic migrations that create and update tables as needed.
For manual migration control:
if err := s.Migrate(ctx); err != nil {
log.Fatal("migration failed:", err)
}Migrations are idempotent -- safe to run on every startup.
Database schema
The PostgreSQL store creates and manages the following tables:
ledger_plans-- Plans with features (JSONB), pricing, currency, and billing cycleledger_subscriptions-- Customer subscriptions with period tracking and trial supportledger_usage_events-- High-throughput usage event ingestion with idempotencyledger_invoices-- Invoices with line items (JSONB) and payment trackingledger_coupons-- Discount coupons with redemption limitsledger_entitlement_cache-- TTL-based entitlement result caching
All tables include tenant_id and app_id columns for multi-tenant isolation, with composite indexes for efficient queries.
Multi-tenancy
All queries enforce tenant isolation via tenant_id and app_id filtering. Composite indexes on (tenant_id, app_id) ensure efficient multi-tenant queries.
Implements store.Store
// Compile-time check
var _ store.Store = (*Store)(nil)Characteristics
| Aspect | Detail |
|---|---|
| Driver | grove ORM + pgdriver |
| Migrations | grove orchestrator with programmatic migrations |
| Transactions | Database-level ACID |
| JSONB columns | Features, pricing, line items, metadata |
| Batch ingestion | Optimized bulk insert for usage events |
| Ping | db.Ping(ctx) |
| Close | Caller-owned -- call db.Close() |
When to use
- Production deployments requiring ACID transactions and full SQL query power.
- Multi-process environments with connection pooling.
- High-throughput billing with usage event ingestion.
See Memory Store for a comparison of implementations.