Arsitektur flat-file textboard seperti cl-bbs menyimpan seluruh thread dan metadata dalam file teks mentah tanpa relational database management system (RDBMS). Masalah utama muncul saat lonjakan traffic: terjadi race condition File I/O yang menyebabkan korupsi data, baris terpotong, atau hilangnya entri thread baru saat beberapa worker proses menulis ke file yang sama secara bersamaan.
Gejala Korupsi Data Thread pada Textboard Flat-File
Pada beban konkurensi tinggi, backend forum flat-file menunjukkan dua anomali utama:
- Interleaved Lines: Dua postingan dari dua user berbeda bercampur dalam satu baris file teks karena buffer write dieksekusi bersamaan.
- Lost Updates: Counter reply atau file metadata index tertimpa (overwritten) oleh thread worker yang membaca nilai lama sebelum worker lain selesai menulis pembaruan (read-modify-write hazard).
Penyebab langsungnya adalah eksekusi append non-atomik dan ketiadaan sinkronisasi level OS antar worker proses yang terpisah (misalnya backend yang berjalan di balik FastCGI, uWSGI, atau multi-process server).
Root Cause: Ketiadaan Advisory Locking dan Batasan O_APPEND
Pengembang sering mengasumsikan flag O_APPEND pada POSIX selalu menjamin penulisan atomik. Anggapan ini keliru pada sistem tertentu:
- Operasi
write()hanya dijamin atomik oleh standar POSIX jika payload berada di bawah ukuranPIPE_BUFdan filesystem mendukung semantik atomik murni. Jika postingan thread berisi teks panjang (melebihi buffer page 4KB), kernel dapat memecahnya menjadi beberapa batch I/O, membuka celah interleaved write. - Metadata file (seperti
threads.jsonatauindex.txt) yang dibaca seluruhnya ke memori, dimodifikasi, lalu ditulis ulang langsung viaopen(..., O_WRONLY|O_TRUNC)pasti mengalami lost updates jika tidak dikunci secara eksklusif.
Analisis Overlapping Syscalls via strace dan lsof
Untuk membuktikan race condition terjadi di tingkat kernel, gunakan strace untuk memantau proses worker yang bersaing menulis ke file target.
# 1. Identifikasi proses worker backend yang membuka file thread
lsof /var/data/bbs/threads/12345.txt
# 2. Trace interaksi system call penulisan file secara konkuren
# Monitor syscall openat, write, read, dan flock
strace -f -tt -T -e trace=openat,write,read,flock,close -p <PID_WORKER_1> -p <PID_WORKER_2>Output strace tipikal saat terjadi race condition tanpa lock:
[PID 2104] 14:02:01.100120 write(4, "Nama: Anon\nIsi: Halo dunia\n", 29) = 29 <0.000045>
[PID 2105] 14:02:01.100135 write(4, "Nama: User2\nIsi: Test...", 25) = 25 <0.000042>
[PID 2104] 14:02:01.100180 write(4, "---\n", 4) = 4 <0.000015>Syscall write PID 2105 tersisip di antara payload data dan delimiter record PID 2104. File data menjadi korup karena pembatas record terpisah.
Implementasi Perbaikan: Advisory Locking dan Atomic Rename
Solusi teknis membutuhkan dua strategi berbeda tergantung pada operasi data:
- Append Postingan: Gunakan
flock(2)dengan flagLOCK_EXsebelum menulis dan pastikan buffer di-flush ke disk menggunakanfsync()sebelum melepas lock. - Update Metadata Index: Tulis data ke file temporer pada filesystem/mount point yang sama, panggil
fsync(), lalu lakukan atomic replace menggunakanrename(2).
1. Sinkronisasi Append via flock
int append_post(const char *thread_path, const char *payload, size_t len) {
int fd = open(thread_path, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) return -1;
// Akuisisi exclusive advisory lock (blocking)
if (flock(fd, LOCK_EX) < 0) {
close(fd);
return -1;
}
ssize_t written = write(fd, payload, len);
// Flush buffer kernel ke media penyimpanan
fdatasync(fd);
// Lepas lock dan tutup file descriptor
flock(fd, LOCK_UN);
close(fd);
return (written == (ssize_t)len) ? 0 : -1;
}2. Atomic Replace untuk File Metadata
Jangan pernah membuka file index langsung dengan mode truncate (O_TRUNC). Tulis perubahan ke file staging terlebih dahulu:
# pseudocode alur atomic metadata replace
temp_file = "/var/data/bbs/index.json.tmp." + pid
write_all_data(temp_file, new_json_bytes)
fsync(temp_file)
# rename(2) adalah operasi atomik dalam filesystem yang sama
rename(temp_file, "/var/data/bbs/index.json")Uji Konkurensi: Verifikasi Bebas Race Condition
Jalankan skrip Bash ini untuk menguji apakah implementasi penulisan postingan aman dari kondisi balapan:
#!/usr/bin/env bash
set -euo pipefail
TARGET_FILE="/tmp/test_thread.txt"
NUM_WORKERS=20
WRITES_PER_WORKER=50
rm -f "$TARGET_FILE"
write_record() {
local worker_id="$1"
local seq="$2"
# Payload valid satu baris lengkap
local payload="worker_${worker_id}_seq_${seq}_padding_xxxxxxxxxxxxxxxxxxxxxx"
# Simulasi eksekusi backend yang menggunakan flock
(
flock -x 200
echo "$payload" >> "$TARGET_FILE"
) 200>"/tmp/test_thread.lock"
}
echo "Menjalankan simulasi konkurensi..."
for w in $(seq 1 $NUM_WORKERS); do
(
for i in $(seq 1 $WRITES_PER_WORKER); do
write_record "$w" "$i"
done
) &
done
wait
echo "Semua worker selesai."
TOTAL_EXPECTED=$(( NUM_WORKERS * WRITES_PER_WORKER ))
TOTAL_ACTUAL=$(wc -l < "$TARGET_FILE")
if [ "$TOTAL_ACTUAL" -eq "$TOTAL_EXPECTED" ]; then
echo "PASS: Integritas data valid ($TOTAL_ACTUAL/$TOTAL_EXPECTED baris)."
else
echo "FAIL: Korupsi terdeteksi! Tercatat: $TOTAL_ACTUAL, Seharusnya: $TOTAL_EXPECTED"
exit 1
fiTrade-off dan Limitasi
Penggunaan advisory locking (flock) menyelesaikan masalah integritas data, tetapi memperkenalkan trade-off throughput. File I/O lock bersifat sequential; saat antrean write memuncak, request latency meningkat karena lock contention. Jika volume penulisan melebihi kapabilitas I/O lokal disk, gantikan flat-file backend dengan SQLite (WAL mode) atau storage engine berbasis LSM-tree.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!