Lonjakan tail latency (p99) pada backend ingest data sering kali berakar dari subsistem filesystem daripada kegagalan aplikasi. Pada arsitektur yang menggunakan Bcachefs versi 1.38.6, penulisan concurrent payload besar dapat memicu I/O stall parah ketika thread worker tertahan di state uninterruptible sleep (D-state). Artikel ini mengulas investigasi bottleneck tersebut, mengidentifikasi root cause pada B-tree lock contention, serta menyajikan solusi teknis di tingkat OS dan aplikasi.

Gejala: P99 Latency Spike dan Worker Blocking

Masalah terdeteksi pada klaster backend ingest yang menangani batch payload besar (file berukuran 32MB hingga 128MB per request). Rata-rata response time tetap berada di kisaran 15ms, namun p99 latency melonjak drastis hingga melebihi 4200ms.

Inspeksi sistem menunjukkan karakteristik berikut:

  • High load average dengan utilisasi CPU aktual yang rendah.
  • Banyak thread worker beralih ke state D saat melakukan pemanggilan syscall fsync() atau write().
  • Kernel stack trace menunjukkan pemblokiran pada fungsi internal locking Bcachefs:
# cat /proc/<pid>/stack
[<0>] bch2_btree_node_lock_write+0x42/0x70 [bcachefs]
[<0>] bch2_trans_commit+0x18a/0x450 [bcachefs]
[<0>] bch2_write_index_default+0xd4/0x1a0 [bcachefs]
[<0>] bch2_write+0x312/0x590 [bcachefs]
[<0>] vfs_write+0x211/0x3e0
[<0>] ksys_write+0x67/0xf0
[<0>] do_syscall_64+0x7b/0x150

Analisis Root Cause: Lock Contention pada B-Tree Node

Bcachefs menggunakan struktur B-tree copy-on-write untuk melacak pemetaan data (extents), alokasi disk, dan metadata inode. Setiap operasi penulisan payload menghasilkan pembaruan index kunci pada btree extents.

Ketika beberapa thread backend menulis potongan blok data secara bersamaan dan memicu flush metadata secara paralel, transaksi Bcachefs (bkey updates) mencoba memodifikasi node B-tree yang sama atau bersebelahan. Dalam rilis 1.38.6, lock contention pada node root atau interior btree meningkat seiring tingginya frekuensi commit transaksi split node dan journal flush, memaksa worker lain antre menunggu write-lock dilepaskan.

Langkah Diagnosa Filesystem

Untuk memastikan akar masalah adalah filesystem internal lock contention dan bukan saturasi physical disk, gunakan tool bawaan Bcachefs.

1. Cek Kapasitas dan Penggunaan Disk

bcachefs fs usage /mnt/storage

Pastikan filesystem tidak sedang mengalami kekurangan ruang alokasi metadata atau terjebak dalam alokasi darurat (reserve exhaustion).

2. Periksa Runtime Counter Kernel

bcachefs show-runtime-counters /mnt/storage

Metrik utama yang perlu diperhatikan:

  • btree_node_lock_fail: Mengindikasikan kegagalan acquiring lock secara instan yang berujung pada thread sleep.
  • btree_split: Frekuensi pemecahan node btree yang tinggi memperpanjang durasi lock eksklusif.
  • journal_flush_blocked: Waktu tunggu worker akibat journal lock yang tertahan.

Solusi dan Mitigasi

Perbaikan dilakukan dalam dua tahap: penyesuaian parameter filesystem dan rekayasa I/O di tingkat aplikasi backend.

1. Optimasi Mount Flag Bcachefs

Sesuaikan mount options di /etc/fstab untuk mengisolasi bottleneck journal dan menurunkan lock contention:

UUID=xxxx-xxxx /mnt/storage bcachefs rw,relatime,metadata_replicas=1,journal_flush_delay=20 0 0

Penjelasan parameter:

  • journal_flush_delay=20: Mengelompokkan penulisan journal hingga 20ms, mengurangi siklus penguncian transaksi diskrit yang berlebihan pada B-tree.
  • Jika menggunakan arsitektur tiered storage atau multi-device, pastikan target data dan metadata dialokasikan pada disk dengan IOPS memadai untuk menghindari starvation saat commit index berlangsung.

2. Chunk Flushing di Level Backend

Hindari pemanggilan fsync() per potongan kecil data atau penulisan acak berukuran variabel tanpa penyelarasan blok. Terapkan chunk alignment dan buffering di level worker (contoh implementasi Go):

package main

import (
	"bufio"
	"io"
	"os"
)

const (
	// Chunk aligned dengan ukuran alokasi blok tipikal
	WriteBufferSize = 1024 * 1024 // 1MB
)

func WriteLargePayload(dstPath string, src io.Reader) error {
	f, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// Gunakan buffered writer untuk mengurangi frekuensi transaksi metadata ke kernel
	bufWriter := bufio.NewWriterSize(f, WriteBufferSize)
	
	buffer := make([]byte, WriteBufferSize)
	for {
		n, rErr := src.Read(buffer)
		if n > 0 {
			if _, wErr := bufWriter.Write(buffer[:n]); wErr != nil {
				return wErr
			}
		}
		if rErr == io.EOF {
			break
		}
		if rErr != nil {
			return rErr
		}
	}

	// Flush buffer aplikasi sekali saja di akhir
	if err := bufWriter.Flush(); err != nil {
		return err
	}

	// fsync tunggal per file lifecycle
	return f.Sync()
}

Verifikasi Hasil

Setelah menerapkan batching buffer 1MB di sisi backend dan mengatur journal_flush_delay, jalankan load test ulang untuk memverifikasi metriks:

  • Nilai btree_node_lock_fail pada bcachefs show-runtime-counters menurun lebih dari 80%.
  • Latency p99 turun dari >4200ms ke nilai stabil di kisaran 45ms–70ms pada throughput yang setara.
  • Thread D-state pada worker pooling tereliminasi, menghilangkan fenomena antrean cascade pada connection pool backend.