Tantangan Skalabilitas Validasi Sender ID

Regulasi telekomunikasi seperti kerangka kerja Anti-Spoofing ACMA mewajibkan agregator SMS memvalidasi kepemilikan dan status pendaftaran Alphanumeric Sender ID sebelum pesan dialirkan ke operator. Pada arsitektur pemrosesan pesan berskala ribuan transaksi per detik (TPS), query validasi registrasi ke database relasional sering menjadi bottleneck utama yang meningkatkan latensi pipeline antrean SMS.

Struktur Data Registrasi Sender ID

Tabel registry sender menampung metadata kepemilikan Sender ID per penyewa (tenant), status verifikasi, dan masa berlaku izin. Skema dasar pada PostgreSQL dirancang sebagai berikut:

CREATE TABLE sender_identities (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL,
    sender_id VARCHAR(11) NOT NULL,
    status VARCHAR(20) NOT NULL, -- 'APPROVED', 'PENDING', 'REJECTED'
    expires_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

Setiap pesan SMS masuk atau keluar memicu query evaluasi status:

SELECT status, expires_at 
FROM sender_identities 
WHERE tenant_id = 'a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d' 
  AND sender_id = 'MYBRAND' 
LIMIT 1;

Tanpa indeks yang tepat, eksekusi query memicu Sequential Scan. Pada jutaan baris data, I/O disk meningkat tajam dan koneksi database pool cepat habis.

Optimasi Database: Composite Indexing dan Covering Index

Query validasi menyaring data berdasarkan kombinasi tenant_id dan sender_id. Solusi pertama adalah menambahkan composite index (B-Tree).

CREATE INDEX idx_sender_identities_tenant_sender 
ON sender_identities (tenant_id, sender_id);

Indeks ini mengubah pencarian dari Sequential Scan menjadi Index Scan. Namun, database engine tetap harus mengakses heap tabel utama untuk membaca nilai kolom status dan expires_at.

Penerapan Covering Index

Untuk menghilangkan disk lookup ke heap tabel, gunakan Covering Index dengan klausa INCLUDE (tersedia di PostgreSQL). Kolom payload evaluasi disimpan langsung pada level leaf node indeks.

CREATE INDEX idx_sender_identities_covering 
ON sender_identities (tenant_id, sender_id) 
INCLUDE (status, expires_at);

Verifikasi rencana eksekusi menggunakan EXPLAIN ANALYZE:

EXPLAIN ANALYZE
SELECT status, expires_at 
FROM sender_identities 
WHERE tenant_id = 'a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d' 
  AND sender_id = 'MYBRAND';

-- Hasil:
-- Index Only Scan using idx_sender_identities_covering on sender_identities (cost=0.42..4.44 rows=1 width=16) (actual time=0.035..0.036 rows=1 loops=1)
-- Planning Time: 0.082 ms
-- Execution Time: 0.051 ms

Hasil Index Only Scan memastikan proses read terjadi murni di memori buffer cache indeks tanpa membaca blok heap tabel data.

Mitigasi Beban Database: Read-Through Caching

Meskipun query covering index sangat cepat, trafik SMS batch berukuran masif tetap dapat membebani database pool. Pola read-through caching dengan Redis memindahkan beban read ke in-memory store.

Alur Validasi Cache

  1. Aplikasi membangun cache key: sender_reg:{tenant_id}:{sender_id}.
  2. Periksa eksistensi key di Redis (operasi O(1)).
  3. Jika cache hit, validasi langsung string status dan timestamp expiry.
  4. Jika cache miss, jalankan query database, simpan hasilnya ke Redis dengan Time-To-Live (TTL), lalu teruskan validasi.
async function validateSenderId(tenantId, senderId, db, redis) {
  const cacheKey = `sender_reg:${tenantId}:${senderId}`;
  
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  const query = `
    SELECT status, expires_at 
    FROM sender_identities 
    WHERE tenant_id = $1 AND sender_id = $2 
    LIMIT 1;
  `;
  const { rows } = await db.query(query, [tenantId, senderId]);
  
  if (rows.length === 0) {
    // Mitigasi cache penetration untuk Sender ID tidak terdaftar
    await redis.set(cacheKey, JSON.stringify({ status: 'NOT_FOUND' }), 'EX', 300);
    return { status: 'NOT_FOUND' };
  }

  const record = rows[0];
  // Cache data valid selama 1 jam (3600 detik)
  await redis.set(cacheKey, JSON.stringify(record), 'EX', 3600);
  
  return record;
}

Trade-off dan Edge Cases

  • Invalidation vs TTL: Perubahan status regulasi (misalnya pembekuan Sender ID oleh regulator) harus segera menghapus cache via event bus (pub/sub atau transactional outbox pattern) daripada mengandalkan masa habis TTL.
  • Cache Penetration: Penyerang dapat mengirim ribuan SMS dengan Sender ID acak untuk memaksa query database terus-menerus. Simpan entry kosong (negative caching) dengan TTL pendek (contoh: 5 menit) untuk memproteksi database.
  • Overhead Indeks: Covering index menambah ukuran penyimpanan indeks pada disk dan sedikit meningkatkan latensi proses registrasi baru (INSERT/UPDATE). Namun, rasio operasi validasi SMS terhadap pendaftaran baru umumnya melebihi 10.000:1, membuat trade-off ini optimal.