Insiden anjloknya harga saham Berkshire Hathaway kelas A sebesar hampir 100% pada Juni 2024 akibat anomali Consolidated Tape Association (CTA) menegaskan kerapuhan sistem finansial saat likuiditas lenyap mendadak. Pada engine order book, fenomena flash crash bukan hanya soal volatilitas harga, melainkan lonjakan throughput pembatalan pesanan (cancel burst), penghapusan likuiditas level dalam, dan eksekusi bertubi-tubi dalam fraksi milidetik. Menjalankan verifikasi skenario ini langsung di pipeline Continuous Integration (CI) memastikan regresi performa dan degradasi memori terdeteksi sebelum masuk produksi.

Anatomi Masalah: Flash Crash dan Beban Ekstrem

Skenario pasar ekstrem memiliki pola pesan yang berbeda dibanding operasi normal:

  • Asimetri Pesan: Rasio pembatalan terhadap eksekusi melonjak drastis. Pasar didominasi pesan CancelOrder dan order jual agresif (Market Sell).
  • Liquidity Vacuum: Order buy limit di harga wajar terkuras habis, memaksa matching engine mengevaluasi rentang harga (tick range) yang jauh lebih lebar.
  • Memory Churn: Pembatalan dan penambahan order frekuensi tinggi menyebabkan fragmentasi memori atau alokasi heap berlebih jika struktur data order book tidak menggunakan pre-allocated memory pool.

Arsitektur Pengujian Deterministik

Runner CI umumnya berbagi sumber daya CPU (noisy neighbor) dan berjalan di lingkungan virtual. Melakukan stress testing berbasis waktu absolut dinding (wall-clock time) seperti mewajibkan latency di bawah 50 mikrodetik di GitHub Actions akan menghasilkan flaky test. Solusinya adalah memisahkan pengujian menjadi dua metrik:

  1. Deterministik State & Invariants: Memastikan integritas buku pesanan, saldo akun, dan validitas eksekusi melalui throughput deterministik berbasis replay log event.
  2. Bounded Degradation & Tail Latency: Menilai waktu pemrosesan menggunakan CPU cycle counter relatif atau batas atas (p99/p99.9) yang diberi toleransi margin deviasi CI.

Struktur Data dan Backpressure Ring Buffer

Matching engine performa tinggi umumnya menerima pesanan via lock-free queue (misalnya Disruptor pattern atau bounded SPSC/MPMC channel). Ketika buffer penuh akibat antrean matching memuncak, engine harus menerapkan backpressure eksplisit (seperti menolak pesan dengan kode error terstandarisasi atau memblokir pengirim secara kooperatif), bukan mengalami unbounded memory leak.

Implementasi Simulasi Beban

Berikut implementasi engine order book minimal berbasis Go beserta stress test harness yang mensimulasikan penipisan likuiditas ekstrem (liquidity dry-up):

package engine_test

import (
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type Side byte
const (
	Buy Side = iota
	Sell
)

type Order struct {
	ID    uint64
	Price uint64
	Qty   uint64
	Side  Side
}

type OrderBook struct {
	mu           sync.Mutex
	bids         map[uint64]uint64 // Price -> Qty
	asks         map[uint64]uint64
	processedOps uint64
}

func NewOrderBook() *OrderBook {
	return &OrderBook{
		bids: make(map[uint64]uint64),
		asks: make(map[uint64]uint64),
	}
}

func (ob *OrderBook) Process(o Order) {
	ob.mu.Lock()
	defer ob.mu.Unlock()

	if o.Side == Buy {
		ob.bids[o.Price] += o.Qty
	} else {
		// Matching sederhana: kurangi dari bid jika harga cocok
		rem := o.Qty
		for bPrice, bQty := range ob.bids {
			if bPrice >= o.Price && rem > 0 {
				if bQty <= rem {
					rem -= bQty
					delete(ob.bids, bPrice)
				} else {
					ob.bids[bPrice] -= rem
					rem = 0
				}
			}
		}
		if rem > 0 {
			ob.asks[o.Price] += rem
		}
	}
	atomic.AddUint64(&ob.processedOps, 1)
}

Berikut adalah harness stress testing yang menguji ketahanan pipeline antrean terhadap ledakan order penjualan skala masif:

func TestStressFlashCrashPipeline(t *testing.T) {
	ob := NewOrderBook()
	numOrders := 100_000
	queueCapacity := 1_024
	orderQueue := make(chan Order, queueCapacity)

	// Inisialisasi likuiditas awal (Order Bids tipis)
	for i := 1; i <= 1_000; i++ {
		ob.Process(Order{ID: uint64(i), Price: uint64(100 + (i % 50)), Qty: 10, Side: Buy})
	}

	var droppedOrders uint64
	var acceptedOrders uint64
	latencies := make([]time.Duration, numOrders)

	// Engine Consumer Loop
	stopConsumer := make(chan struct{})
	go func() {
		for o := range orderQueue {
			ob.Process(o)
		}
		close(stopConsumer)
	}()

	// Producer: Simulasi serbuan order agresif (Market Dump)
	start := time.Now()
	for i := 0; i < numOrders; i++ {
		t0 := time.Now()
		order := Order{
			ID:    uint64(2000 + i),
			Price: uint64(50 + (i % 60)), // Melibas level harga buy
			Qty:   5,
			Side:  Sell,
		}

		select {
		case orderQueue <- order:
			latencies[acceptedOrders] = time.Since(t0)
			acceptedOrders++
		default:
			// Backpressure aktif: tolak order jika ring buffer penuh
			atomic.AddUint64(&droppedOrders, 1)
		}
	}
	close(orderQueue)
	<-stopConsumer
	totalDuration := time.Since(start)

	// Assertion Kriteria Kegagalan CI
	if acceptedOrders == 0 {
		t.Fatal("Critical: Engine mengalami starvation total")
	}

	dropRatio := float64(droppedOrders) / float64(numOrders)
	throughput := float64(acceptedOrders) / totalDuration.Seconds()

	t.Logf("Throughput: %.2f ops/sec, Dropped: %d (%.2f%%)", throughput, droppedOrders, dropRatio*100)

	// Kriteria 1: Engine harus memproses minimal batas minimum throughput di CI
	if throughput < 50_000 {
		t.Errorf("Degradasi throughput terdeteksi: %.2f ops/sec < 50,000", throughput)
	}

	// Kriteria 2: Backpressure harus menolak ketika penuh, bukan deadlock
	if dropRatio > 0.95 {
		t.Errorf("Antrean macet: Rasio drop order terlalu tinggi: %.2f%%", dropRatio*100)
	}
}

Mitigasi Flaky Test di Environment CI

Untuk menjaga eksekusi tes tetap reliabel di pipeline otomatis (seperti GitHub Actions atau GitLab CI), terapkan strategi berikut:

  • Gunakan Percentile Ketimbang Nilai Absolut: Hindari max_latency < 1ms. Gunakan p95 < target * ci_multiplier untuk mengakomodasi jitter CPU pada shared runner.
  • Karantina Benchmark dari Unit Test: Jalankan stress testing pada tahap pipeline tersendiri menggunakan runner dengan alokasi CPU dedicated. Gunakan tagging atau build constraints (seperti //go:build integration).
  • Memory Leak Detection: Ambil snapshot alokasi memori (runtime.ReadMemStats atau valgrind/sanitizer) sebelum dan sesudah injeksi beban. Periksa pertumbuhan alokasi yang tidak terlepas meski antrean telah dikosongkan.

Integrasi Pipeline CI

Contoh konfigurasi GitHub Actions workflow untuk mengeksekusi suite pengujian stres order book:

name: OrderBook Stress Test
on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  stress-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - name: Run Race Detector
        run: go test -race -short ./...
      - name: Run Deterministic Stress Scenarios
        run: go test -v -run=TestStressFlashCrashPipeline -timeout 3m ./...
        env:
          GOGC: "100"

Pengujian stres yang deterministik mencegah regresi performa masuk ke rantai produksi, memastikan sistem perdagangan tetap tangguh bahkan di tengah volatilitas ekstrem.