Skip to content

[TASK-SIPT-DRAGONFLYDB-ENV-VALIDATION] Kompatibilitas DragonflyDB Cluster In-Memory Cache dan Fail-Fast Environment Validation Bootstrap pada SIPT Backend ​

Modul: SIPT/PAB | Tipe: Refactoring | Stage Saat Ini: DEVELOPMENT | Tanggal Dokumen: 2026-09-15

DEVELOPMENT STAGE

Dokumen ini sedang berada dalam tahap DEVELOPMENT. Penyesuaian konfigurasi konektivitas DragonflyDB/Redis Cluster dan utilitas fail-fast validation startup masih aktif di lingkungan pengembangan (Development).


1. Informasi Tugas & Referensi Bisnis ​

  • Task ID / Nomor Referensi: TASK-SIPT-DRAGONFLYDB-ENV-VALIDATION

  • Sumber Instruksi: Modernisasi Infrastruktur In-Memory Database (DragonflyDB) & Standardisasi Bootstrap

  • Dokumen Referensi / Lampiran: Tidak ada

  • Task / Dependensi Terkait (Cross-Service): —

  • Modul Terkait: SIPT/PAB

  • Service / Repository: sipt-backend

  • PIC Pengembang: Muhamad Firly Ramadan, Hermansyah Efendi

  • Ringkasan Non-Teknis (Executive Summary): Repo sipt-backend melakukan standardisasi pada lapisan database in-memory (cache) dan inisialisasi bootstrap server:

    1. Konektivitas cache disesuaikan agar kompatibel penuh dan optimal dengan engine DragonflyDB. Klien Redis dialihkan dari mode kluster (Redis.Cluster) menjadi single-node instance berkecepatan tinggi, dilengkapi structured child logger (redis-database), serta mekanisme retry strategy bertahap dengan batas maksimum 5 percobaan.
    2. Mengeliminasi potensi celah keamanan ReDoS pada pemrosesan prefix key serta memvalidasi bahwa alamat host target dan penamaan prefix lingkungan selalu terdefinisi secara deterministik.
    3. Menerapkan mekanisme proteksi startup (fail-fast validation) melalui modul env-validator.js. Jika terdapat variabel lingkungan penting (seperti kredensial atau host koneksi) yang belum didefinisikan saat deployment, server akan seketika membatalkan proses startup dan mencatat rincian variabel yang hilang, mencegah aplikasi beroperasi dalam kondisi cacat (partially broken state).
  • Latar Belakang & Ringkasan Kebutuhan: Dalam rangka standarisasi infrastruktur backend LNSW menuju high-throughput in-memory datastore, konektivitas Redis pada sipt-backend disesuaikan agar kompatibel penuh dengan implementasi engine DragonflyDB. Standardisasi ini mencakup penyelarasan penamaan variabel environment Redis (REDIS_NODES, REDIS_KEY_PREFIX, REDIS_USERNAME, REDIS_PASSWORD), eliminasi kerentanan ReDoS pada sanitasi prefix key, serta penyederhanaan koneksi menjadi single-node client yang selaras dengan arsitektur multi-threaded DragonflyDB. Selain itu, guna mencegah runtime crash di tengah operasional akibat variabel lingkungan yang belum didefinisikan pada server deployment, diimplementasikan mekanisme fail-fast bootstrap melalui utilitas env-validator.js yang memvalidasi seluruh environment schema sebelum server Express menerima request.

  • Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):

    • [x] Refactoring helper inisialisasi koneksi redis-cluster.js untuk mendukung format node DragonflyDB / Redis modern
    • [x] Switch klien driver dari Redis.Cluster ke single-node new Redis(...) untuk efisiensi native DragonflyDB
    • [x] Integrasi structured Pino child logger { service_name: "redis-database" } dan penyempurnaan retryStrategy dengan batas MAX_RETRY = 5
    • [x] Standardisasi nama variabel environment Redis: REDIS_NODES, REDIS_KEY_PREFIX, REDIS_USERNAME, REDIS_PASSWORD
    • [x] Mitigasi kerentanan ReDoS pada sanitasi trailing colon REDIS_KEY_PREFIX dengan mengganti regex greedy replace(/:+$/, '') menjadi deterministic loop stripTrailingColons
    • [x] Implementasi modul validasi startup app/utils/env-validator.js dengan kamus ENV_SCHEMA (fail-fast: abort bootstrap jika variabel wajib tidak terdefinisi)
    • [x] Penambahan variabel INAPORT_TIMEOUT (dengan default value 5000ms) dan PASS_EXCEL pada schema validasi lingkungan
    • [x] Penyesuaian test suite mock environment dan retry strategy pada tests/helpers/redis-cluster.test.js serta tests/helpers/cache-service.test.js

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-09-02 11:50Hermansyah Efendi, Muhamad Firly RamadanDEPLOYEDBranch: development (Commit: e4b1e98 .. d534fca)
2Staging (UAT)——PENDING—
3Production——PENDING—

Riwayat Update Log ​

TanggalEnvironmentPerubahanPIC
2026-09-15DevelopmentInisialisasi technical audit log untuk adaptasi DragonflyDB cluster dan fail-fast environment validationMuhamad Firly Ramadan, Hermansyah Efendi
2026-09-15DevelopmentRefactoring konektivitas Redis client menjadi single node untuk DragonflyDB, integrasi child logger Pino, dan retryStrategy (commit d534fca)Hermansyah Efendi

3. Analisis Teknis & Rincian Perubahan ​

  • Alur Logika & Bisnis:

    • Agnostik Terhadap Topologi Datastore (REDIS_CLUSTER_* ➔ REDIS_*): Sebelumnya, penamaan variabel environment menggunakan prefix spesifik REDIS_CLUSTER_* (REDIS_CLUSTER_NODES, REDIS_CLUSTER_USERNAME, REDIS_CLUSTER_PASSWORD). Engine DragonflyDB umumnya dijalankan sebagai high-performance multi-threaded single instance atau emulasi kluster. Penggantian nama menjadi REDIS_NODES, REDIS_USERNAME, dan REDIS_PASSWORD membuat konfigurasi bersifat generik dan agnostik—dapat menunjuk ke single node instance (non-cluster) maupun multi-node cluster tanpa modifikasi kode driver.
    • Transisi ke Single Node Client (new Redis(...)) untuk DragonflyDB: DragonflyDB dirancang dengan arsitektur multi-threaded shared-nothing yang berjalan sebagai single-instance process berkemampuan throughput tinggi. Penggunaan Redis.Cluster client pada arsitektur ini memicu overhead routing kluster (MOVED/ASK redirects) dan state ping yang tidak diperlukan. Driver disederhanakan menggunakan single-node client (new Redis) yang mengambil entri host pertama dari clusterNodes[0], dengan enableReadyCheck: true, key prefix <prefix>:<NODE_ENV>:, serta retryStrategy bertahap (Math.min(times * 100, 2000)) dengan batas MAX_RETRY = 5.
    • Observability Granular dengan Structured Pino Child Logger: Inisialisasi logger menggunakan parentLogger.child({ service_name: "redis-database" }), memastikan seluruh event koneksi (Connected to Redis Database), retry count/delay, dan penanganan kegagalan socket (Redis Connection Error) diperkaya dengan metadata service_name, error message, dan error code untuk memudahkan pemantauan dan filtering pada Grafana/Loki.
    • Fleksibilitas Parsing Host (resolveHost): Pada implementasi lama, kode langsung memanggil .split(',') tanpa proteksi sanitasi string atau port default. Pada implementasi baru, helper resolveHost memfilter string kosong, memotong whitespace (trim()), serta memberikan fallback default port 6379 jika developer hanya menuliskan host tanpa port (misal 10.x.x.x otomatis dipetakan menjadi { host: '10.x.x.x', port: 6379 }). Hal ini memfasilitasi integrasi single-node DragonflyDB tanpa perlu konfigurasi multi-address yang berlebihan.
    • Standardisasi Prefix (REDIS_CLIENT_ID ➔ REDIS_KEY_PREFIX) & Mitigasi ReDoS: Variabel REDIS_CLIENT_ID sebelumnya rancu karena mengindikasikan identitas koneksi client, padahal fungsinya merupakan namespace key redis. Variabel ini diganti menjadi REDIS_KEY_PREFIX. Selain itu, implementasi lama rentan menghasilkan key double-colon (misal sipt::development:) jika developer mengisi variabel dengan akhiran tanda titik dua (sipt:). Diimplementasikan fungsi deterministik stripTrailingColons berbasis while loop dan string method native endsWith(':') berbobot linier $O(n)$ non-backtracking untuk menggantikan regex greedy replace(/:+$/, ''), sehingga mengeliminasi risiko kerentanan Regular Expression Denial of Service (ReDoS) sekaligus menjamin key prefix selalu deterministik: <prefix>:<NODE_ENV>:.
    • Strict Fail-Fast Bootstrap (app/utils/env-validator.js & server.js): Keempat variabel baru (REDIS_NODES, REDIS_KEY_PREFIX, REDIS_USERNAME, REDIS_PASSWORD) didaftarkan ke dalam kamus ENV_SCHEMA dengan atribut required: true. Modul validasi ini diimpor pada baris awal eksekusi server.js sebelum inisialisasi Express app. Jika ada variabel wajib yang tidak terdefinisi atau string kosong, logger mencatat daftar missing keys dan aplikasi langsung melakukan terminasi paksa (process.exit(1)). Ini mencegah server beroperasi dalam kondisi cacat (partially broken state).
    • Ekspansi Skema Validasi (cefa9c1): Menambahkan variabel INAPORT_TIMEOUT (dengan fallback default 5000ms) untuk mencegah hanging request saat berkomunikasi dengan INAPORT, serta PASS_EXCEL (required) untuk kebutuhan proteksi dan enkripsi berkas pelaporan Excel.
  • Tabel Komparasi Perubahan Environment Variable:

    Konfigurasi Lama (Legacy Redis Cluster)Konfigurasi Baru (DragonflyDB / Standar LNSW)Sifat di ENV_SCHEMAKeterangan & Format Nilai
    REDIS_CLUSTER_NODESREDIS_NODESRequiredHost target. Mendukung single node (10.239.8.x:6379 atau 10.239.8.x) maupun multi-node (host1:6379,host2:6379). Fallback port: 6379.
    REDIS_CLUSTER_USERNAMEREDIS_USERNAMERequiredUsername autentikasi datastore.
    REDIS_CLUSTER_PASSWORDREDIS_PASSWORDRequiredPassword autentikasi datastore.
    REDIS_CLIENT_IDREDIS_KEY_PREFIXRequiredNamespace prefix key. Otomatis dinormalisasi tanpa trailing colon menjadi format <prefix>:<NODE_ENV>:.
  • Risiko Teknis & Dampak Tersembunyi (Risks & Hidden Costs):

    • Breaking Boot Failure: Jika berkas environment di server deployment (Development / Staging / Production) masih mempertahankan nama variabel lama (REDIS_CLUSTER_*), service backend dipastikan langsung crash / abort (process.exit(1)) saat proses booting dijalankan. Hal ini terjadi karena env-validator.js dieksekusi sebelum Express listen.
    • Key Namespace Shift: Jika nilai REDIS_KEY_PREFIX berbeda dengan REDIS_CLIENT_ID sebelumnya, cache lama atau key session yang tersimpan tidak akan terbaca lagi (terjadi isolated cache miss).
  • Perubahan Database (DDL / DML):

    sql
    -- Tidak ada perubahan skema database (DDL/DML) untuk task ini.
    -- Perubahan murni pada layer driver in-memory cache/cluster dan startup validation bootstrap.
  • Kontrak API & Response Payload: Tidak ada penambahan atau perubahan kontrak API HTTP. Perubahan ini beroperasi pada level internal runtime, driver cache, dan bootstrap server.


4. Bukti Teknis (Technical Proof & Audit Trail) ​

  • Repository: sipt-backend
  • Branch Utama: development
  • Merge Request / PR: -

Daftar Commit Terkait (Multi-Commit Trail) ​

Commit IDBranchAuthorTanggal & JamRingkasan Perubahan
e4b1e98f7ee1625c818e52c86e17c2436cb29540developmentMuhamad Firly Ramadan2026-08-28 17:36feat(redis): add dragonflydb compability config
d497869774402fc9de9e4dd325ca40511f3071e1developmentMuhamad Firly Ramadan2026-08-29 17:35feat(env-validation): add env validation utility
9b50f1f72b43db862da087913ea02b754b467ef0developmentMuhamad Firly Ramadan2026-09-02 10:53fix(redis): replace ReDoS-prone trailing-colon regex with endsWith loop
cefa9c10298e5517b3ae540ee50d1318e0264c33developmentHermansyah Efendi2026-09-02 11:50feat(env-validation): add inaport timeout and pass excel to schema
d534fca423c9f38da126be8f4bfac72399fa8a11developmentHermansyah Efendi2026-09-15 12:00refactor(redis): switch redis client to single node for dragonflydb

Cuplikan Kode / Implementasi Inti ​

javascript
// File: app/helpers/redis-cluster.js
import 'dotenv/config';
import Redis from 'ioredis';
import parentLogger from './logger.js';

const logger = typeof parentLogger.child === 'function'
  ? parentLogger.child({ service_name: "redis-database" })
  : parentLogger;

const MAX_RETRY = 5;

const requireEnv = (name) => {
  const value = process.env[name];
  if (!value) {
    throw new Error(`[redis] Missing required env var: ${name}`);
  }
  return value;
};

const stripTrailingColons = (value) => {
  let result = value;
  while (result.endsWith(':')) {
    result = result.slice(0, -1);
  }
  return result;
};

const resolveHost = (raw) => {
  if (!raw || typeof raw !== "string" || !raw.trim()) {
    return [];
  }
  return raw
    .split(',')
    .map((entry) => entry.trim())
    .filter(Boolean)
    .map((entry) => {
      const [host, port] = entry.split(':');
      return { host, port: port ? Number(port) : 6379 };
    });
};

const clusterNodes = resolveHost(requireEnv('REDIS_NODES'));
if (!clusterNodes.length) {
  throw new Error('[redis] No valid nodes found in REDIS_NODES');
}

const envTag = requireEnv('NODE_ENV');
const keyPrefix = `${stripTrailingColons(requireEnv('REDIS_KEY_PREFIX'))}:${envTag}:`;

const targetNode = clusterNodes[0]; // single node karena sudah memakai DragonFly

const redis = new Redis({
  host: targetNode.host,
  port: targetNode.port,
  username: process.env.REDIS_USERNAME || undefined,
  password: process.env.REDIS_PASSWORD || undefined,
  enableReadyCheck: true,
  keyPrefix,
  retryStrategy: (times) => {
    if (times > MAX_RETRY) {
      logger.error(`Redis: Retry limit reached (${MAX_RETRY}x).`);
      return null; // stop retrying
    }

    const delay = Math.min(times * 100, 2000); // Delay bertahap, max 2s
    logger.warn(`Redis: Retry #${times}, retrying in ${delay}ms...`);
    return delay;
  },
});

redis.on('connect', () => {
  logger.info('Connected to Redis Database');
});

redis.on('error', (err) => {
  logger.error({
    err,
    message: err.message,
    code: err.code,
  }, 'Redis Connection Error');
});

// DEVOPS DEFENSE LAYER:
redis.flushall = () => { throw new Error('CRITICAL SECURITY: FLUSHALL is forbidden.'); };
redis.flushdb = () => { throw new Error('CRITICAL SECURITY: FLUSHDB is forbidden.'); };
redis.keys = () => { throw new Error('CRITICAL SECURITY: KEYS is forbidden. Write a custom SCAN function instead.'); };

export default redis;
javascript
// File: app/utils/env-validator.js (Fail-Fast Schema Check)
function validateEnv() {
  const schemaKeys = Object.keys(ENV_SCHEMA);
  const missingRequired = [];

  for (const key of schemaKeys) {
    const spec = ENV_SCHEMA[key];
    const val = process.env[key];
    const isEmpty = val === undefined || val === null || val.trim() === "";

    if (!isEmpty) continue;

    if (spec.required) {
      missingRequired.push(key);
    } else if (spec.default !== undefined) {
      process.env[key] = spec.default;
    }
  }

  if (missingRequired.length > 0) {
    logger.error(
      { missingRequired },
      "Environment validation failed: required variables are not set or empty. Application boot aborted."
    );
    process.exit(1);
  }
}

5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification) ​

A. Verifikasi Unit Test (Development Stage) ​

  • Test Suite Files: tests/helpers/redis-cluster.test.js, tests/helpers/cache-service.test.js
  • Status Eksekusi CLI: PASSED (32 tests passed)
javascript
// File: tests/helpers/redis-cluster.test.js (Single-Node Redis Mock & Retry Strategy)
it('exports a redis instance', async () => {
  const mod = await import('../../app/helpers/redis-cluster.js');
  expect(mod.default).toBeDefined();
});

it('retryStrategy returns delay when under MAX_RETRY', () => {
  const delay = capturedRetryStrategy(1);
  expect(typeof delay === 'number' || delay === null).toBe(true);
});

it('retryStrategy returns null after exceeding MAX_RETRY', () => {
  const result = capturedRetryStrategy(6);
  expect(result).toBeNull();
});
text
 RUN  v4.1.11 D:/WORK/LNSW/sipt-backend

 ✓ tests/helpers/redis-cluster.test.js (6 tests) 22ms
 ✓ tests/helpers/cache-service.test.js (26 tests) 15ms

 Test Files  2 passed (2)
      Tests  32 passed (32)
   Start at  09:54:46
   Duration  381ms (transform 84ms, setup 0ms, import 132ms, tests 37ms, environment 0ms)

B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook) ​

  • Cakupan / Surface Pengujian: Worker / DB / Cron Only & API / Postman Only (Bootstrap Verification)
  • Kebutuhan Akses / Role Akun: Akses server / console log environment deployment
  • Hasil Pengujian UAT: PENDING
  • Catatan QA: —
Skenario Pengujian QA (Test Scenarios) ​
  1. Skenario 1 — Verifikasi Fail-Fast Missing Required Env (Negative Bootstrap):

    • Pre-kondisi: Sengaja kosongkan salah satu variabel wajib (misal REDIS_NODES atau PASS_EXCEL).
    • Trigger / Request: Jalankan server dengan node server.js atau npm start.
    • Ekspektasi Response: Server langsung berhenti (process.exit(1)) sebelum membuka port Express, dan menampilkan log error terstruktur: "Environment validation failed: required variables are not set or empty. Application boot aborted.".
    • Verifikasi Database / Log: Server tidak running dalam status cacat (partially broken state).
  2. Skenario 2 — Verifikasi Koneksi DragonflyDB / Redis Multi-Node (Happy Path):

    • Pre-kondisi: Seluruh variabel lingkungan REDIS_NODES, REDIS_KEY_PREFIX, REDIS_USERNAME, REDIS_PASSWORD telah dikonfigurasi sesuai cluster target.
    • Trigger / Request: Jalankan server backend npm start.
    • Ekspektasi Response: Server booting sukses, console menampilkan log "Environment validation completed successfully." diikuti "Connected to Redis Cluster".
    • Verifikasi Database / Log: Operasi cache dan lock submission cooldown berjalan normal pada instance DragonflyDB/Redis target dengan key prefix yang dinormalisasi secara benar tanpa karakter colon ganda (::).
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PENDING",
  "note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA dan verifikasi konektivitas DragonflyDB cluster."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend