Menjalankan pipeline analisis Git commit secara paralel sering memicu race condition ketika beberapa worker mencoba memproses referensi atau repositori yang sama secara bersamaan. Tanpa sinkronisasi terdistribusi, konflik penulisan cache, pembacaan parsial dari disk, dan redundansi komputasi dapat merusak konsistensi data hasil analisis.
Akar Masalah: Disk I/O Parsial dan Lock Contention pada Worker Git
Saat worker mengeksekusi operasi Git berat seperti git checkout, git fetch, atau parsing commit object graph ke disk lokal, direktori .git dan struktur workspace berada dalam kondisi transien. Jika worker kedua mulai membaca repositori pada saat worker pertama belum selesai menulis lock file Git (index.lock) atau membaca status proses via /proc, eksekusi akan gagal dengan error fatal: Unable to create lock file atau menghasilkan cache analisis yang korup akibat pembacaan parsial.
Masalah utama yang timbul meliputi:
- Lock Contention: Banyak worker memicu operasi analisis pada commit hash yang sama sehingga CPU dan I/O disk terbuang sia-sia.
- Dirty State Disk: Pembacaan parsial file metadata sebelum commit log selesai di-unpack atau diindeks ke disk.
- Inkonsistensi Cache: Worker menulis hasil parsial ke basis data atau Redis cache dengan state yang tidak valid akibat interupsi proses lain.
Mengapa Mutex Lokal Tidak Cukup: Redis Redlock sebagai Solusi
Mutex bawaan bahasa pemrograman (seperti sync.Mutex pada Go atau threading locks di Python) hanya mengamankan memori dalam satu instance/kontainer. Dalam arsitektur horizontal di mana antrian pesan (seperti RabbitMQ, Redis Streams, atau SQS) dikonsumsi oleh banyak node worker terpisah, diperlukan distributed lock.
Algoritma Redis Redlock menyediakan jaminan mutual exclusion pada distributed system tanpa bergantung pada single point of failure (SPOF). Redlock menguji akuisisi kunci pada N master node Redis independen menggunakan batas waktu akuisisi yang jauh lebih kecil daripada lock validity time (lease duration). Kunci dianggap valid jika berhasil didapatkan dari minimal (N/2) + 1 node dalam kurun waktu yang ditentukan.
Implementasi Distributed Lock dengan Redlock
Contoh berikut mendemonstrasikan implementasi penguncian antrian worker berbasis Redlock menggunakan Node.js (TypeScript) dengan library redlock dan ioredis:
import Redis from 'ioredis';
import Redlock, { Lock } from 'redlock';
const redisNodes = [
new Redis({ host: 'redis-node-1.internal', port: 6379 }),
new Redis({ host: 'redis-node-2.internal', port: 6379 }),
new Redis({ host: 'redis-node-3.internal', port: 6379 }),
];
const redlock = new Redlock(redisNodes, {
driftFactor: 0.01,
retryCount: 3,
retryDelay: 200,
retryJitter: 100,
});
async function processGitCommitAnalysis(repoId: string, commitHash: string): Promise<void> {
const lockKey = `locks:git:repo:${repoId}:commit:${commitHash}`;
const ttlMs = 30000; // 30 detik lock lease duration
let lock: Lock;
try {
lock = await redlock.acquire([lockKey], ttlMs);
} catch (err) {
// Lock gagal didapat: tugas sedang diproses worker lain atau contention tinggi
console.warn(`Worker dilewati: commit ${commitHash} sedang diproses worker lain.`);
return;
}
// Heartbeat interval untuk perpanjangan lease secara otomatis
const extensionInterval = setInterval(async () => {
try {
lock = await lock.extend(ttlMs);
} catch (extendErr) {
console.error('Gagal memperpanjang lease lock:', extendErr);
}
}, 10000);
try {
await executeGitAnalysis(repoId, commitHash);
} finally {
clearInterval(extensionInterval);
try {
await lock.release();
} catch (releaseErr) {
console.error('Gagal melepaskan lock secara eksplisit:', releaseErr);
}
}
}
async function executeGitAnalysis(repoId: string, commitHash: string): Promise<void> {
// Eksekusi git command, parse log, dan perbarui state cache
}Penanganan Lock Lease Timeout dan Heartbeat
Salah satu skenario kegagalan fatal pada distributed locking adalah jika waktu eksekusi task melebihi TTL lock sebelum eksekusi selesai. Ketika lock habis masa berlakunya tanpa sepengetahuan worker pertama, worker lain dapat mengambil alih lock dan memicu modifikasi disk yang sama secara bersamaan.
Solusi yang diterapkan mencakup:
- Auto-Extension (Heartbeat): Memperpanjang TTL secara berkala via pemanggilan
extend()selama proses analisis Git masih aktif di worker, seperti pada implementasisetIntervaldi atas. - Fencing Token: Menyertakan token mononik inkremental pada setiap write-operation ke persistent store. Operasi ditolak jika versi token lebih rendah dari state terakhir yang disimpan.
Fallback Worker Recovery tanpa Duplicate Processing
Ketika worker crash akibat OOM (Out Of Memory) saat mengeksekusi diff commit berukuran masif, lock akan otomatis expired setelah TTL berakhir. Namun, antrian pesan tidak boleh serta-merta mengeksekusi ulang task tanpa validasi idempotensi.
Gunakan alur fallback berikut:
- Idempotency Check via Cache: Sebelum mengakuisisi lock atau sesudah lock didapat, periksa apakah output commit analysis sudah tercatat di datastore dengan status
SUCCESS. - Dead Letter Queue (DLQ) dengan Recheck: Jika task gagal dan dikembalikan ke antrian, verifikasi status hash Git di intermediate state. Jika task crash lebih dari 3 kali berturut-turut, pindahkan commit ID ke DLQ untuk isolasi debugging dan cegah infinite crash loop.
- Pembersihan Workspace: Eksekusi sanitasi direktori kerja lokal (seperti
git clean -fdxatau menghapus working tree parsial) sebelum worker memulai checkout baru agar disk tidak berada dalam state korup akibat proses yang terhenti paksa.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!