Anti-Pattern Database: Read Replica Menganggur dan Salah Kaprah Penggunaannya
11 min read

Anti-Pattern Database: Read Replica Menganggur dan Salah Kaprah Penggunaannya

Keberadaan read replica sering dianggap tanda sistem yang sudah scalable dan mature. Infrastruktur sudah disiapkan, biaya sudah dibayar, dan tim engineering merasa arsitekturnya sudah benar. Tapi ada anti-pattern yang lebih berbahaya justru karena tidak terlihat jelas: read replica tersedia, berjalan, dibayar setiap bulan — namun hampir tidak mendapatkan traffic dari aplikasi. Atau lebih buruk lagi, satu-satunya yang menggunakannya adalah tim data untuk query analitik, sementara aplikasi tetap sepenuhnya membaca dari master. Kondisi ini bukan sekadar suboptimal. Ini adalah pemborosan biaya sekaligus indikasi desain yang tidak tuntas — dan artikel ini membahas mengapa itu terjadi, apa konsekuensinya, dan bagaimana seharusnya read replica ditempatkan dalam arsitektur yang benar.

Cara Kerja Read Replica dan Replication Lag

Sebelum membahas anti-pattern, penting untuk memahami mekanisme dasar replikasi. Sebagian besar managed database — MySQL, PostgreSQL, Amazon RDS, Aurora — menggunakan asynchronous replication sebagai default untuk read replica.

sequenceDiagram
    participant App as Aplikasi
    participant Master as Master DB
    participant Replica as Read Replica

    App->>Master: WRITE (INSERT/UPDATE/DELETE)
    Master-->>App: Commit berhasil
    Master--)Replica: Kirim binary log (async)
    Note over Master,Replica: Replication lag: 10ms–beberapa detik
    Replica--)Replica: Apply log
    App->>Replica: READ (SELECT)
    Note over Replica: Data mungkin belum ter-update

Konsekuensi dari model ini:

  • Semua write selalu masuk ke master
  • Replica menerima perubahan dengan delay — disebut replication lag
  • Lag ini bisa berkisar dari puluhan milidetik hingga beberapa detik tergantung beban write dan jarak network

Replication lag adalah karakteristik sistem terdistribusi, bukan bug. Ia tidak bisa dihilangkan sepenuhnya — hanya bisa dihadapi secara arsitektural. Read replica bukan salinan real-time; ia adalah eventually consistent copy dari master.

Replication lag bisa meningkat drastis saat master mengalami write spike — misalnya batch insert besar, migrasi data, atau traffic lonjakan. Sistem yang tidak pernah mengukur lag replication di production sering terkejut saat menemukan lag mencapai menit, bukan milidetik, saat beban tinggi.

Read Replica Idle — Anti-Pattern yang Tidak Terlihat

Anti-pattern ini mudah lolos dari perhatian karena sistem tetap berjalan normal. Tidak ada error, tidak ada alert, tidak ada keluhan dari user. Yang ada hanya tagihan cloud yang terus berjalan untuk resource yang tidak memberikan nilai.

Tanda-tanda read replica idle:

  ✗ CPU replica konsisten single-digit (< 5%) sementara master di atas 50%
  ✗ Connections ke replica hampir nol sepanjang hari
  ✗ Query per second di replica jauh lebih rendah dari master
  ✗ Satu-satunya traffic ke replica adalah dari cron job atau tim data
  ✗ Engineer tidak tahu endpoint mana yang membaca dari replica

Mengapa Ini Terjadi

Penyebab paling umum bukan ketidaktahuan tentang replica — tim biasanya tahu ada replica. Penyebabnya adalah kekhawatiran terhadap replication lag yang kemudian dijadikan alasan untuk tidak menggunakan replica sama sekali:

"Kalau kita baca dari replica, datanya bisa stale."
"Lebih aman baca dari master saja biar konsisten."
"Nanti user komplain lihat data yang beda."

Argumen-argumen ini tidak salah secara teknis, tapi salah secara arsitektur. Mereka menyamakan semua read operation dengan critical read operation — padahal keduanya sangat berbeda.

Biaya Nyata dari Anti-Pattern Ini

Contoh estimasi biaya (AWS RDS, db.r6g.xlarge, us-east-1):

  Master:  ~$300/bulan
  Replica: ~$300/bulan (identik)

  Jika replica hanya dipakai 5% kapasitas:
  → $285/bulan dibuang sia-sia
  → $3.420/tahun untuk infrastruktur yang tidak memberikan value

  Jika ada 2 replica idle:
  → $6.840/tahun pemborosan langsung

Ironisnya, biaya replica ini sering tidak dipertanyakan karena dianggap sebagai “biaya HA (high availability)”. Padahal replica untuk HA dan replica untuk read offload adalah dua kebutuhan berbeda dengan desain berbeda.


Konsistensi Adalah Masalah Use Case, Bukan Alasan Menolak Replica

Pertanyaan yang tepat bukan “Bagaimana menghilangkan replication lag?” melainkan “Read mana yang benar-benar membutuhkan strong consistency?”

Jawabannya: jauh lebih sedikit dari yang diasumsikan kebanyakan tim.

Use CaseButuh Strong Consistency?Aman ke Replica?Alasan
User profile tepat setelah update✓ Ya✗ TidakUser harus melihat perubahan yang baru disimpan
Form submit + halaman konfirmasi✓ Ya✗ TidakData harus mencerminkan write yang baru terjadi
Transaksi finansial✓ Ya✗ TidakKonsistensi adalah keharusan bisnis
Dashboard statistik✗ Tidak✓ YaSelisih beberapa detik tidak material
List produk / katalog✗ Tidak✓ YaData jarang berubah dalam hitungan detik
Feed / timeline✗ Tidak✓ YaEventual consistency adalah norm di feed
Hasil pencarian✗ Tidak✓ YaSedikit stale masih acceptable
Laporan dan reporting✗ Tidak✓ YaLaporan biasanya sudah agregasi data historis
Halaman detail produk✗ Tidak✓ YaData produk jarang berubah real-time

Pada sistem e-commerce atau aplikasi SaaS yang umum, lebih dari 70% read operation tidak membutuhkan strong consistency dan aman diarahkan ke replica.

flowchart TD
    A[Incoming READ Request] --> B{Apakah read ini terjadi\nlangsung setelah WRITE\noleh user yang sama?}
    B -- Ya --> C{Apakah ketidakcocokan\ndata akan terlihat\njelas oleh user?}
    C -- Ya --> D[→ Baca dari MASTER]
    C -- Tidak --> E[→ Baca dari REPLICA]
    B -- Tidak --> F{Apakah data yang\nsedikit stale bisa\nditerima secara bisnis?}
    F -- Ya --> E
    F -- Tidak --> D

Strategi Menghadapi Replication Lag

Setelah memutuskan bahwa replica harus digunakan, langkah berikutnya adalah menangani kasus di mana consistency memang dibutuhkan — tanpa harus mengirim semua traffic ke master.

Read Your Own Write (RYOW)

RYOW adalah strategi paling umum untuk menangani ketidaksinkronan setelah write. Intinya sederhana: setelah user melakukan write, arahkan read berikutnya ke master untuk sementara waktu.

-- ANTI-PATTERN: baca dari replica langsung setelah write
BEGIN TRANSACTION;
  UPDATE users SET display_name = 'Budi Santoso' WHERE id = 123;
COMMIT;
-- Lalu langsung:
SELECT * FROM users WHERE id = 123;  -- ke replica, bisa dapat data lama!

-- BENAR: RYOW — baca dari master dalam window setelah write
BEGIN TRANSACTION;
  UPDATE users SET display_name = 'Budi Santoso' WHERE id = 123;
COMMIT;
-- Tandai sesi: "user ini baru saja melakukan write"
-- Read pertama ke master, read berikutnya (setelah window) boleh ke replica
SELECT * FROM users WHERE id = 123;  -- ke master, pasti fresh

Implementasi RYOW di application layer bisa sesederhana menyimpan flag di session atau cache:

Session flag: user_just_wrote = true (TTL: 2 detik)

Read routing logic:
  IF session.user_just_wrote = true
    → baca dari MASTER
  ELSE
    → baca dari REPLICA

Read Routing Berdasarkan Use Case

Pendekatan yang lebih eksplisit adalah mendefinisikan routing langsung di repository atau service layer:

-- Contoh routing eksplisit di query layer

-- Query untuk dashboard (eventual consistency OK)
-- Gunakan read replica connection
SELECT
    COUNT(*) AS total_orders,
    SUM(amount) AS total_revenue,
    DATE(created_at) AS order_date
FROM orders
WHERE created_at >= NOW() - INTERVAL 30 DAY
GROUP BY DATE(created_at);
-- → Directed to: REPLICA

-- Query setelah user update profile (strong consistency required)
-- Gunakan master connection
SELECT id, email, display_name, avatar_url
FROM users
WHERE id = :userId;
-- → Directed to: MASTER

Time-Based Routing

Untuk sistem yang tidak bisa melacak state per-user dengan mudah, time-based routing memberikan jaminan sederhana:

Aturan routing:
  - Read dalam 2 detik pertama setelah write oleh session yang sama → MASTER
  - Read setelah 2 detik → REPLICA

Implementasi:
  - Simpan timestamp write terakhir per session
  - Saat ada read request, cek selisih waktu
  - Jika < threshold → master, jika >= threshold → replica

Adaptive Routing Berdasarkan Lag Monitoring

Strategi paling tangguh adalah membuat routing bersifat adaptif berdasarkan kondisi replikasi aktual:

flowchart LR
    A[Read Request] --> B{Cek replication\nlag saat ini}
    B -- "Lag < 500ms\n(acceptable)" --> C[→ REPLICA]
    B -- "Lag ≥ 500ms\n(too high)" --> D[→ MASTER fallback]
    C --> E[Response]
    D --> E
-- Query untuk memonitor replication lag (PostgreSQL)
SELECT
    client_addr AS replica_host,
    state,
    EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))
        AS lag_seconds,
    sent_lsn,
    replay_lsn
FROM pg_stat_replication;

-- Jika lag_seconds > threshold, fallback ke master untuk sementara
Tentukan threshold lag yang relevan untuk aplikasi kamu berdasarkan SLA bisnis, bukan asumsi. Aplikasi finansial mungkin hanya toleran terhadap lag < 100ms, sementara platform konten bisa toleran sampai beberapa detik. Ukur dulu, baru tentukan threshold.

Kesalahan Desain: Tim Data Membaca dari Read Replica

Ini adalah anti-pattern kedua yang sering terjadi bersamaan dengan replica idle — dan ironisnya, keduanya saling memperburuk.

OLTP dan OLAP Adalah Dua Dunia Berbeda

KarakteristikOLTP (RDS/Aurora)OLAP (Redshift/BigQuery)
Tujuan utamaMelayani transaksi aplikasiAnalitik dan reporting
Pola queryPoint read, small result setFull scan, heavy aggregation
LatencyHarus rendah (< 100ms)Bisa lebih tinggi (detik–menit)
Optimasi storageRow-orientedColumn-oriented
ConcurrencyRibuan transaksi kecilSedikit query besar
Beban tipikalWrite-heavyRead-heavy analitik

Query analitik yang dijalankan tim data memiliki karakteristik yang bertolak belakang dengan query aplikasi — full table scan, join besar, agregasi jutaan baris, dan runtime yang bisa mencapai menit. Ketika ini dijalankan di read replica:

flowchart TD
    subgraph "Kondisi Salah"
        A1[Tim Data] -- "Query analitik\nberat" --> R1[Read Replica]
        APP1[Aplikasi] -- "Query aplikasi\nnormal" --> R1
        R1 -- "CPU spike,\nlatency naik" --> R1
        R1 -- "Replica lag\nmeningkat" --> R1
    end

    subgraph "Kondisi Benar"
        A2[Tim Data] -- "Query analitik" --> DW[Data Warehouse\nRedshift / BigQuery]
        APP2[Aplikasi] -- "Read non-kritis" --> R2[Read Replica]
        APP3[Aplikasi] -- "Write / Critical read" --> M2[Master DB]
        M2 -- "CDC / ETL pipeline" --> DW
    end

Dampak Query Analitik di Read Replica

Skenario nyata yang sering terjadi:

  1. Tim data menjalankan query "ringan" di replica:
     SELECT * FROM orders
     JOIN order_items ON orders.id = order_items.order_id
     JOIN products ON order_items.product_id = products.id
     WHERE orders.created_at >= '2024-01-01'
     ORDER BY orders.created_at DESC;
     -- Ini full scan orders + join, bisa jutaan baris

  2. CPU replica naik ke 80-90%

  3. Replication lag mulai meningkat — replica tidak bisa apply
     log dari master secepat biasanya karena sibuk melayani query berat

  4. Aplikasi yang seharusnya membaca dari replica kini mendapat
     data yang semakin stale

  5. Jika lag melewati threshold, semua traffic fallback ke master

  6. Master yang seharusnya hanya melayani write kini mendapat
     seluruh read traffic — persis kondisi sebelum ada replica

Satu query analitik bisa menghapus semua manfaat dari keberadaan replica.

Data Warehouse adalah Tempat yang Tepat

Data warehouse dirancang spesifik untuk pola kerja tim data:

Keunggulan columnar storage untuk analitik:

  Query:  SELECT SUM(amount), AVG(amount), COUNT(*)
          FROM orders WHERE status = 'completed'

  Row-oriented (OLTP):
    → Baca seluruh row termasuk kolom yang tidak diperlukan
    → Inefficient untuk agregasi kolom tunggal

  Columnar (OLAP):
    → Hanya baca kolom 'amount' dan 'status'
    → 5-10x lebih cepat untuk query agregasi
    → Kompresi lebih baik (data sejenis disimpan berdekatan)

Selain performa, pemisahan ini memberikan separation of concern yang jelas: OLTP melayani aplikasi, OLAP melayani analitik. Gangguan di satu sisi tidak merembet ke sisi lain.


Arsitektur yang Sehat

Arsitektur database yang mature memisahkan traffic berdasarkan sifat dan kebutuhan tiap jenis operasi:

flowchart TD
    subgraph "Application Layer"
        WEB[Web / API Server]
    end

    subgraph "Database Layer"
        MASTER[Master DB\nWrite + Critical Read]
        REPLICA1[Read Replica 1\nApp Read Offload]
        REPLICA2[Read Replica 2\nApp Read Offload]
    end

    subgraph "Analytics Layer"
        ETL[CDC / ETL Pipeline\nDebezium / Fivetran]
        DW[Data Warehouse\nRedshift / BigQuery / Snowflake]
    end

    subgraph "Consumers"
        ANALYST[Tim Data / BI Tools]
    end

    WEB -- "Write & Critical Read" --> MASTER
    WEB -- "Non-critical Read" --> REPLICA1
    WEB -- "Non-critical Read" --> REPLICA2
    MASTER -- "Binary log stream" --> ETL
    ETL --> DW
    ANALYST --> DW

Setiap komponen melayani peran yang jelas:

MASTER       → satu-satunya target write; critical read setelah write
READ REPLICA → offload non-critical read dari aplikasi; tidak untuk analitik
DATA WAREHOUSE → satu-satunya target query analitik tim data

Query langsung dari tim data ke OLTP hanya boleh untuk:
  ✓ Debugging ad-hoc yang tidak bisa dilakukan di warehouse
  ✓ Verifikasi data real-time yang sangat mendesak
  ✗ Bukan untuk query reguler atau laporan rutin

Dampak terhadap Cost dan Reliability

Desain yang benar memberikan dampak langsung pada dua hal yang paling diperhatikan: biaya dan ketersediaan sistem.

Dengan arsitektur yang tepat:

  BEFORE:
  - Master: CPU 70%, under pressure dari read + write
  - Replica: CPU 5%, hampir idle (biaya terbuang)
  - Tim data: query ke replica, sesekali spike

  AFTER:
  - Master: CPU 40%, hanya melayani write + critical read
  - Replica: CPU 40–60%, aktif melayani read aplikasi
  - Data warehouse: melayani semua analitik tim data

  Hasil:
  ✓ Master lebih stabil → write latency lebih rendah
  ✓ Replica punya utilisasi nyata → ROI terbayar
  ✓ Query analitik tidak mengganggu operasional aplikasi
  ✓ Replication lag lebih stabil karena replica tidak dibebani query berat

Cost reduction datang sebagai efek samping dari desain yang benar — bukan dari pemotongan paksa.


Checklist Audit Read Replica

UTILISASI:
  □ Cek QPS (query per second) replica vs master — rasio target minimal 40:60
  □ Cek CPU utilization replica — jika konsisten < 10%, traffic tidak terdistribusi
  □ Cek active connections ke replica dari application servers
  □ Identifikasi semua endpoint/query yang masih baca dari master tanpa alasan

KONSISTENSI:
  □ Petakan semua read operation per endpoint: mana yang butuh strong consistency?
  □ Implementasikan RYOW untuk semua operation write-then-read dalam session yang sama
  □ Set replication lag monitoring dengan alert jika melewati threshold bisnis
  □ Tentukan fallback behavior saat lag tinggi: otomatis ke master atau error?

PEMISAHAN OLTP/OLAP:
  □ Audit apakah ada query analitik reguler yang berjalan di replica
  □ Pastikan tim data memiliki akses ke data warehouse, bukan ke OLTP
  □ Set up CDC atau ETL pipeline dari master ke data warehouse
  □ Dokumentasikan kebijakan: kapan boleh query langsung ke OLTP?

ARSITEKTUR:
  □ Konfigurasi connection pool terpisah untuk master dan replica
  □ Read routing logic terdokumentasi dan di-enforce di repository layer
  □ Tidak ada hardcoded master connection string di query yang seharusnya ke replica

Ringkasan

  • Read replica idle adalah pemborosan nyata — resource dibayar penuh tapi tidak memberikan value; CPU single-digit di replica adalah tanda arsitektur yang tidak tuntas.
  • Replication lag adalah karakteristik, bukan bug — pertanyaan yang tepat bukan bagaimana menghilangkannya, tapi read mana yang benar-benar butuh strong consistency.
  • Mayoritas read tidak butuh strong consistency — dashboard, list data, feed, search, dan reporting semua aman diarahkan ke replica; hanya read tepat setelah write yang butuh master.
  • Read Your Own Write (RYOW) adalah strategi paling praktis untuk menangani kasus write-then-read: arahkan ke master sementara dalam window waktu tertentu setelah write.
  • Adaptive routing berbasis lag monitoring membuat sistem tangguh — jika replication lag melewati threshold, fallback otomatis ke master.
  • Tim data membaca dari read replica adalah kesalahan desain — query analitik bersifat full scan dan bisa menyebabkan CPU spike yang meningkatkan replication lag dan mengganggu seluruh aplikasi.
  • Data warehouse adalah tempat yang tepat untuk analitik — columnar storage, parallel execution, dan isolasi dari traffic aplikasi menjadikannya jauh lebih efisien untuk query tim data.
  • Arsitektur sehat = pemisahan jelas: master untuk write dan critical read, replica untuk non-critical read aplikasi, data warehouse untuk semua analitik.

Portofolio