[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-VALIDATIONSumber Instruksi: Modernisasi Infrastruktur In-Memory Database (DragonflyDB) & Standardisasi Bootstrap
Dokumen Referensi / Lampiran: Tidak ada
Task / Dependensi Terkait (Cross-Service): —
Modul Terkait:
SIPT/PABService / Repository:
sipt-backendPIC Pengembang: Muhamad Firly Ramadan, Hermansyah Efendi
Ringkasan Non-Teknis (Executive Summary): Repo
sipt-backendmelakukan standardisasi pada lapisan database in-memory (cache) dan inisialisasi bootstrap server:- 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. - Mengeliminasi potensi celah keamanan ReDoS pada pemrosesan prefix key serta memvalidasi bahwa alamat host target dan penamaan prefix lingkungan selalu terdefinisi secara deterministik.
- 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).
- Konektivitas cache disesuaikan agar kompatibel penuh dan optimal dengan engine DragonflyDB. Klien Redis dialihkan dari mode kluster (
Latar Belakang & Ringkasan Kebutuhan: Dalam rangka standarisasi infrastruktur backend LNSW menuju high-throughput in-memory datastore, konektivitas Redis pada
sipt-backenddisesuaikan 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 utilitasenv-validator.jsyang memvalidasi seluruh environment schema sebelum server Express menerima request.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Refactoring helper inisialisasi koneksi
redis-cluster.jsuntuk mendukung format node DragonflyDB / Redis modern - [x] Switch klien driver dari
Redis.Clusterke single-nodenew Redis(...)untuk efisiensi native DragonflyDB - [x] Integrasi structured Pino child logger
{ service_name: "redis-database" }dan penyempurnaanretryStrategydengan batasMAX_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_PREFIXdengan mengganti regex greedyreplace(/:+$/, '')menjadi deterministic loopstripTrailingColons - [x] Implementasi modul validasi startup
app/utils/env-validator.jsdengan kamusENV_SCHEMA(fail-fast: abort bootstrap jika variabel wajib tidak terdefinisi) - [x] Penambahan variabel
INAPORT_TIMEOUT(dengan default value 5000ms) danPASS_EXCELpada schema validasi lingkungan - [x] Penyesuaian test suite mock environment dan retry strategy pada
tests/helpers/redis-cluster.test.jssertatests/helpers/cache-service.test.js
- [x] Refactoring helper inisialisasi koneksi
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-09-02 11:50 | Hermansyah Efendi, Muhamad Firly Ramadan | DEPLOYED | Branch: development (Commit: e4b1e98 .. d534fca) |
| 2 | Staging (UAT) | — | — | PENDING | — |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-09-15 | Development | Inisialisasi technical audit log untuk adaptasi DragonflyDB cluster dan fail-fast environment validation | Muhamad Firly Ramadan, Hermansyah Efendi |
| 2026-09-15 | Development | Refactoring 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 spesifikREDIS_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 menjadiREDIS_NODES,REDIS_USERNAME, danREDIS_PASSWORDmembuat 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. PenggunaanRedis.Clusterclient pada arsitektur ini memicu overhead routing kluster (MOVED/ASKredirects) dan state ping yang tidak diperlukan. Driver disederhanakan menggunakan single-node client (new Redis) yang mengambil entri host pertama dariclusterNodes[0], denganenableReadyCheck: true, key prefix<prefix>:<NODE_ENV>:, sertaretryStrategybertahap (Math.min(times * 100, 2000)) dengan batasMAX_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 metadataservice_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, helperresolveHostmemfilter string kosong, memotong whitespace (trim()), serta memberikan fallback default port 6379 jika developer hanya menuliskan host tanpa port (misal10.x.x.xotomatis 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: VariabelREDIS_CLIENT_IDsebelumnya rancu karena mengindikasikan identitas koneksi client, padahal fungsinya merupakan namespace key redis. Variabel ini diganti menjadiREDIS_KEY_PREFIX. Selain itu, implementasi lama rentan menghasilkan key double-colon (misalsipt::development:) jika developer mengisi variabel dengan akhiran tanda titik dua (sipt:). Diimplementasikan fungsi deterministikstripTrailingColonsberbasis while loop dan string method nativeendsWith(':')berbobot linier $O(n)$ non-backtracking untuk menggantikan regex greedyreplace(/:+$/, ''), 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 kamusENV_SCHEMAdengan atributrequired: true. Modul validasi ini diimpor pada baris awal eksekusiserver.jssebelum 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 variabelINAPORT_TIMEOUT(dengan fallback default 5000ms) untuk mencegah hanging request saat berkomunikasi dengan INAPORT, sertaPASS_EXCEL(required) untuk kebutuhan proteksi dan enkripsi berkas pelaporan Excel.
- Agnostik Terhadap Topologi Datastore (
Tabel Komparasi Perubahan Environment Variable:
Konfigurasi Lama (Legacy Redis Cluster) Konfigurasi Baru (DragonflyDB / Standar LNSW) Sifat di ENV_SCHEMAKeterangan & Format Nilai REDIS_CLUSTER_NODESREDIS_NODESRequired Host target. Mendukung single node ( 10.239.8.x:6379atau10.239.8.x) maupun multi-node (host1:6379,host2:6379). Fallback port: 6379.REDIS_CLUSTER_USERNAMEREDIS_USERNAMERequired Username autentikasi datastore. REDIS_CLUSTER_PASSWORDREDIS_PASSWORDRequired Password autentikasi datastore. REDIS_CLIENT_IDREDIS_KEY_PREFIXRequired Namespace 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 karenaenv-validator.jsdieksekusi sebelum Express listen. - Key Namespace Shift: Jika nilai
REDIS_KEY_PREFIXberbeda denganREDIS_CLIENT_IDsebelumnya, cache lama atau key session yang tersimpan tidak akan terbaca lagi (terjadi isolated cache miss).
- Breaking Boot Failure: Jika berkas environment di server deployment (Development / Staging / Production) masih mempertahankan nama variabel lama (
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 ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
e4b1e98f7ee1625c818e52c86e17c2436cb29540 | development | Muhamad Firly Ramadan | 2026-08-28 17:36 | feat(redis): add dragonflydb compability config |
d497869774402fc9de9e4dd325ca40511f3071e1 | development | Muhamad Firly Ramadan | 2026-08-29 17:35 | feat(env-validation): add env validation utility |
9b50f1f72b43db862da087913ea02b754b467ef0 | development | Muhamad Firly Ramadan | 2026-09-02 10:53 | fix(redis): replace ReDoS-prone trailing-colon regex with endsWith loop |
cefa9c10298e5517b3ae540ee50d1318e0264c33 | development | Hermansyah Efendi | 2026-09-02 11:50 | feat(env-validation): add inaport timeout and pass excel to schema |
d534fca423c9f38da126be8f4bfac72399fa8a11 | development | Hermansyah Efendi | 2026-09-15 12:00 | refactor(redis): switch redis client to single node for dragonflydb |
Cuplikan Kode / Implementasi Inti
// 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;// 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)
// 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();
}); 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)
Skenario 1 — Verifikasi Fail-Fast Missing Required Env (Negative Bootstrap):
- Pre-kondisi: Sengaja kosongkan salah satu variabel wajib (misal
REDIS_NODESatauPASS_EXCEL). - Trigger / Request: Jalankan server dengan
node server.jsataunpm 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).
- Pre-kondisi: Sengaja kosongkan salah satu variabel wajib (misal
Skenario 2 — Verifikasi Koneksi DragonflyDB / Redis Multi-Node (Happy Path):
- Pre-kondisi: Seluruh variabel lingkungan
REDIS_NODES,REDIS_KEY_PREFIX,REDIS_USERNAME,REDIS_PASSWORDtelah 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 (
::).
- Pre-kondisi: Seluruh variabel lingkungan
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA dan verifikasi konektivitas DragonflyDB cluster."
}