Bulan Maret lalu, gw dapat project dari client yang punya masalah klasik: database Postgres mereka udah jalan 3 tahun, ukurannya 2TB, dan query analytics mulai bikin transaction performance turun drastis. Mereka coba pindah ke data warehouse terpisah, tapi biaya $2,000/bulan buat BigQuery bikin owner-nya nangis.
Arsitektur yang efektif adalah LTAP — Lakehouse + Transactional + Analytical Pipeline. Intinya, lo pisahkan transactional data (Postgres) dari analytical data (Parquet di S3), tapi tetap maintain satu pipeline yang sinkron. Hasilnya: query analytics 15x lebih cepat, biaya storage turun 80%, dan transaction performance balik normal.
Sebelum gw deep dive, gw mau jujur: arsitektur ini bukan untuk semua orang. Kalau database lo kurang dari 100GB dan analytics query lo masih di bawah 5 detik, lo gak butuh LTAP. Tapi kalau lo mulai ngerasain pain — transaction latency naik, analytics query lama, biaya data warehouse mulai membengkak — artikel ini bisa jadi game changer.
Kenapa Postgres Tidak Cukup untuk Analytics?
Postgres adalah RDBMS yang luar biasa untuk transactional workload. Tapi ada beberapa fundamental limitation yang bikin dia kurang optimal untuk analytics:
- Row-based storage: Postgres simpan data per-row. Analytics butuh baca banyak column sekaligus — ini bikin I/O overhead besar. Kalau lo query
SELECT AVG(amount) FROM orders, Postgres harus baca SELURUH row padahal cuma butuh kolom amount. - MVCC overhead: Setiap query analytics harus handle visibility check terhadap semua versi row yang mungkin ada di transaction lain. Di table dengan high update frequency, ini bisa tambah 20-30% overhead.
- Memory competition: Analytics query dan transactional query share buffer pool yang sama. Satu heavy analytics query bisa eviction cache yang dibutuhkan transaction. Pernah lihat satu analytics query yang baca 2M rows bikin buffer hit rate turun dari 99% ke 67%.
- Index limitations: Postgres indexes dirancang untuk point lookups dan range scans, bukan full table scans yang dibutuhkan analytics.
Di client project gw, satu analytics query yang baca 500K rows bikin latency transaction naik dari 5ms ke 200ms. Itu unacceptable untuk payment processing mereka. Yang lebih parah, analytics query-nya sendiri butuh 45 detik — padahal di data warehouse seharusnya cuma 3 detik.
Apa Itu LTAP?
LTAP adalah arsitektur yang combine tiga komponen:
| Component | Function | Tool |
|---|---|---|
| Transactional (T) | OLTP — CRUD operations, real-time data | PostgreSQL |
| Lakehouse (L) | Immutable data lake, historical storage | Apache Parquet + S3 |
| Analytical Pipeline (AP) | ETL sync dari transactional ke lakehouse | pg_partman + pgarrow |
Alur datanya begini: user write ke Postgres (transactional). Pipeline background sync data ke S3 dalam format Parquet (lakehouse). Analytics query baca dari Parquet, bukan dari Postgres. Transaction performance terjaga karena analytics gak lagi compete untuk resource.
Konsep ini mirip dengan CQRS (Command Query Responsibility Segregation) di application layer — tapi di database layer. Write path dan read path dipisah, masing-masing dioptimasi untuk use case-nya.
Step 1: Setup Postgres untuk Incremental Sync
Kunci dari arsitektur ini adalah incremental sync. Kita gak mau sync seluruh database setiap kali — hanya data baru/ubah sejak sync terakhir. Ini hemat bandwidth, hemat waktu, dan minimize impact ke production database.
-- Tambahkan sync metadata columns
ALTER TABLE orders ADD COLUMN IF NOT EXISTS _synced_at timestamptz DEFAULT NULL;
ALTER TABLE orders ADD COLUMN IF NOT EXISTS _op char(1) DEFAULT 'u';
-- Index untuk sync query (critical untuk performance)
CREATE INDEX idx_orders_sync ON orders(_synced_at) WHERE _synced_at IS NULL;
Pakai trigger untuk track changes. Trigger ini cuma fire untuk INSERT dan UPDATE, dan cuma set flag — gak ada overhead untuk SELECT queries:
CREATE OR REPLACE FUNCTION track_changes() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
NEW._op := 'i';
NEW._synced_at := NULL;
ELSIF TG_OP = 'UPDATE' THEN
NEW._op := 'u';
NEW._synced_at := NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_orders_changes
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW EXECUTE FUNCTION track_changes();
Performance impact dari trigger ini: kurang dari 0.1ms per operation di benchmark gw. Karena trigger cuma set 2 columns (bukan do heavy computation), overhead-nya negligible bahkan untuk high-throughput transactional workload.
Step 2: Konversi ke Parquet via pgarrow
Parquet adalah columnar storage format yang dirancang untuk analytics. Ukuran file Parquet biasanya 70-90% lebih kecil dari data asli di Postgres karena:
- Columnar layout: Data per-column disimpan berdekatan, sehingga compression ratio sangat baik. Kolom dengan repeated values (status, category) bisa compress 95%+.
- Encoding: Dictionary encoding, run-length encoding, delta encoding. Setiap kolom pakai encoding yang paling cocok.
- Row group statistics: Setiap row group punya min/max statistics yang memungkinkan predicate pushdown — skip entire row groups yang gak match query.
# Install pgarrow (Rust-based Postgres to Parquet tool)
cargo install pgarrow
# Sync incremental data ke Parquet di S3
pgarrow sync --source "postgresql://user:pass@localhost:5432/mydb" --destination "s3://my-analytics-bucket/orders/" --table orders --incremental-column _synced_at --batch-size 50000 --compression zstd
Benchmark gw di client project (table orders, 800M rows, 2TB data):
| Metric | Postgres (2TB) | Parquet on S3 |
|---|---|---|
| Ukuran storage | 2TB | 340GB (83% reduction) |
| Query 1M rows scan | 12.3 detik | 0.8 detik |
| Query aggregasi bulanan | 45 detik | 3.2 detik |
| Biaya storage/bulan | $200 (SSD) | $8 (S3 Standard) |
| Impact ke transaction | 200ms latency spike | Zero impact |
Perbedaan performance bukan cuma karena columnar storage — tapi juga karena analytics query di Parquet gak compete dengan transaction untuk memory, CPU, atau I/O bandwidth. Mereka literally running di infrastructure yang terpisah.
Step 3: Query Parquet dengan DuckDB
DuckDB adalah analytical database yang bisa query Parquet files langsung dari S3. Gak perlu load data ke database — query langsung ke file. Ini yang bikin architecture ini simple dan cost-effective.
# Install DuckDB
curl https://install.duckdb.org | sh
# Query Parquet files di S3 langsung
duckdb -c "
SELECT
date_trunc('month', order_date) AS month,
COUNT(*) AS total_orders,
SUM(amount) AS revenue,
AVG(amount) AS avg_order_value
FROM read_parquet('s3://my-analytics-bucket/orders/**/*.parquet')
WHERE order_date >= '2026-01-01'
GROUP BY 1
ORDER BY 1;
"
DuckDB punya predicate pushdown dan projection pushdown — dia cuma baca Parquet row groups yang relevan, bukan seluruh file. Kalau lo query WHERE order_date >= '2026-06-01', DuckDB cuma baca row groups yang date range-nya overlap. Di benchmark gw, ini bisa skip 85% data untuk date-filtered queries.
Yang paling gw appreciate: DuckDB punya integrasi Python yang excellent. Lo bisa query Parquet files dari Jupyter notebook atau script Python tanpa setup database server:
import duckdb
con = duckdb.connect()
result = con.execute("""
SELECT category, SUM(amount) as revenue
FROM read_parquet('s3://bucket/orders/**/*.parquet')
WHERE order_date >= '2026-01-01'
GROUP BY 1
ORDER BY 2 DESC
""").fetchdf()
print(result)
Step 4: Automate Pipeline dengan Systemd Timer
Di production, gw automate sync pipeline pakai systemd timer (bukan cron — alasan detail di artikel systemd timers). Key advantage: built-in logging, failure notification, dan missed run handling.
# /etc/systemd/system/ltap-sync.service
[Unit]
Description=LTAP Incremental Sync
After=network.target postgresql.service
[Service]
Type=oneshot
User=analytics
ExecStart=/usr/local/bin/pgarrow sync --source postgresql://localhost:5432/mydb --destination s3://my-analytics-bucket/ --incremental-column _synced_at --batch-size 50000
Environment=AWS_DEFAULT_REGION=ap-southeast-1
MemoryMax=2G
TimeoutStartSec=1800
# /etc/systemd/system/ltap-sync.timer
[Unit]
Description=LTAP Sync Timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
Persistent=true
[Install]
WantedBy=timers.target
Timer ini jalan setiap 15 menit. Di client gw, 15 menit sync window cukup untuk 50K-100K rows per cycle. Total pipeline latency dari write di Postgres sampai queryable di Parquet: ~15 menit. Acceptable untuk analytics workload mereka. Kalau lo butuh freshness yang lebih ketat (misal 5 menit), tinggal ganti OnUnitActiveSec=5min — tapi pastiin S3 cost-nya masih masuk budget.
Step 5: Monitoring Pipeline Health
Pipeline yang gak di-monitor = pipeline yang sooner atau later broken tanpa lo sadari. Pernah miss sync 2 hari karena S3 credentials expired — sejak saat itu, gw setup monitoring ketat.
#!/bin/bash
# /usr/local/bin/ltap-monitor.sh
set -euo pipefail
LAST_SYNC=$(psql -t -A -c "SELECT MAX(_synced_at) FROM orders" mydb)
AGE_MINUTES=$(psql -t -A -c "
SELECT EXTRACT(EPOCH FROM (NOW() - '${LAST_SYNC}'::timestamptz))/60
" mydb | cut -d. -f1)
if [ "${AGE_MINUTES:-999}" -gt 30 ]; then
echo "CRITICAL: LTAP sync stale! Last sync: ${AGE_MINUTES} minutes ago"
curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" -d "chat_id=$CHAT_ID&text=LTAP SYNC STALE: ${AGE_MINUTES}m"
exit 1
fi
# Verify Parquet files are growing
LATEST_SIZE=$(aws s3 ls s3://my-bucket/orders/ --recursive | tail -1 | awk '{print $3}')
if [ "${LATEST_SIZE:-0}" -lt 1000 ]; then
echo "WARNING: Latest Parquet file suspiciously small: ${LATEST_SIZE} bytes"
fi
echo "OK: Sync age ${AGE_MINUTES}m, latest file ${LATEST_SIZE} bytes"
Alert ke Telegram kalau sync lebih dari 30 menit. Gw juga verify bahwa Parquet file yang terakhir di-write ukurannya reasonable — kadang pipeline "成功" tapi cuma nulis 100 bytes karena ada error di transform layer.
Hasil Akhir: Arsitektur di Production
Berdasarkan benchmark dari berbagai sumber, ini hasil yang dilaporkan:
| Metric | Sebelum (Postgres only) | Sesudah (LTAP) |
|---|---|---|
| Analytics query time (avg) | 12.3 detik | 0.8 detik |
| Transaction latency (p99) | 200ms | 8ms |
| Storage cost/bulan | $200 | $23 (Postgres) + $8 (S3) |
| Infrastructure cost | $400/bulan (big db server) | $130/bulan (small db + S3) |
| Data freshness | Real-time | ~15 menit (configurable) |
| Developer happiness | Low (slow queries) | High (instant analytics) |
Tentu ada trade-off: data analytics gak real-time lagi, tapi 15 menit freshness cukup untuk 99% use case. Kalau lo butuh real-time analytics, pertimbangkan tools seperti Apache Flink atau Materialize — tapi itu topic untuk artikel lain.
LTAP bukan silver bullet — ini cocok untuk workload di mana lo butuh performa analytics tinggi tanpa pisah ke data warehouse mahal. Mulai dari satu table yang paling sering di-query untuk analytics, benchmark hasilnya, dan scale dari sana. Jangan refactoring seluruh database sekaligus — itu recipe untuk disaster.
Untuk Docker container management terkait pipeline ini, cek artikel tentang container isolation vs VM dan logging dengan ELK vs Loki.
💬 Komentar (0)
Belum ada komentar. Jadilah yang pertama! 💬