Integrasi backend ke Service Provider Interface (SPI) broker bursa luar negeri menuntut toleransi kesalahan yang ketat. Lonjakan latensi jaringan lintas batas atau degradasi broker dapat memicu timeout cascading, penolakan order (order rejection), hingga inkonsistensi saldo pengguna. Implementasi deployment canary yang dipadukan dengan circuit breaker berbasis metrik latensi p99 dan rasio error menjadi lapisan proteksi utama untuk mencegah kerugian finansial sistemik.
Arsitektur Observabilitas Latency SPI Broker
Saat merutekan order ke broker eksternal, pemantauan latensi rata-rata (mean/average) menyembunyikan anomali eksekusi. Analisis harus berfokus pada persentil tinggi (p95 dan p99) untuk mendeteksi antrean jaringan (head-of-line blocking) atau throttling dari SPI broker. Metrik latensi diekspos menggunakan histogram Prometheus dengan bucket terukur.
package main
import (
"context"
"errors"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/sony/gobreaker"
)
var (
spiLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "spi_execution_duration_seconds",
Help: "Distribusi latensi panggilan eksekusi SPI broker.",
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0},
}, []string{"broker", "action"})
orderRejections = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "spi_order_rejection_total",
Help: "Jumlah order yang ditolak SPI broker.",
}, []string{"broker", "reason_code"})
)
type BrokerClient struct {
cb *gobreaker.CircuitBreaker
httpClient *http.Client
}
func NewBrokerClient() *BrokerClient {
st := gobreaker.Settings{
Name: "US-Stock-SPI",
MaxRequests: 5,
Interval: 10 * time.Second,
Timeout: 30 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 20 && failureRatio >= 0.20
},
}
return &BrokerClient{
cb: gobreaker.NewCircuitBreaker(st),
httpClient: &http.Client{Timeout: 3 * time.Second},
}
}
func (c *BrokerClient) ExecuteOrder(ctx context.Context, orderPayload []byte) error {
start := time.Now()
_, err := c.cb.Execute(func() (interface{}, error) {
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "https://spi.broker.internal/v1/orders", nil)
res, doErr := c.httpClient.Do(req)
if doErr != nil {
return nil, doErr
}
defer res.Body.Close()
if res.StatusCode >= 500 || res.StatusCode == http.StatusTooManyRequests {
orderRejections.WithLabelValues("interactive_brokers", "upstream_5xx").Inc()
return nil, errors.New("upstream failure")
}
return nil, nil
})
spiLatency.WithLabelValues("interactive_brokers", "buy").Observe(time.Since(start).Seconds())
return err
}Prometheus Alert dan Automated Canary Rollback
Saat merilis patch integrasi trading (misalnya penyesuaian payload FIX atau REST protocol), deployment canary dibatasi ke 5-10% traffic. Sistem routing (seperti Flagger, Argo Rollouts, atau ingress controller) harus membaca metrik Prometheus dan otomatis membatalkan canary jika batas aman terlampaui.
groups:
- name: stock-spi-canary.rules
rules:
- alert: CanarySPIp99LatencyHigh
expr: |
histogram_quantile(0.99, sum(rate(spi_execution_duration_seconds_bucket{canary="true"}[1m])) by (le)) > 1.5
for: 30s
labels:
severity: critical
annotations:
summary: "Latensi Canary p99 SPI melebihi ambang batas toleransi 1.5s"
- alert: CanaryOrderRejectionSpike
expr: |
sum(rate(spi_order_rejection_total{canary="true"}[1m]))
/
sum(rate(spi_execution_duration_seconds_count{canary="true"}[1m])) > 0.05
for: 30s
labels:
severity: critical
annotations:
summary: "Rejection rate order pada instance canary melebihi 5%"Webhook controller menangkap alert ini untuk segera mengubah bobot traffic canary ke 0% (traffic shifting reset) dan menjaga pod versi stable tetap melayani transaksi pengguna.
Mitigasi Split-State Saat Eksekusi Terputus
Masalah paling berbahaya pada integrasi bursa adalah timeout jaringan di sisi backend ketika order sesungguhnya telah diterima oleh mesin pencocokan bursa (matching engine). Circuit breaker yang terbuka secara lokal berisiko meninggalkan status split-state: backend mencatat order GAGAL, namun broker mengeksekusi PEMBELIAN, memicu pengurangan saldo ganda atau selisih margin.
- Client-Assigned ClOrdID: Kirim Client Order ID unik (UUIDv7 atau Snowflake ID) di header request ke SPI. Broker wajib mengabaikan request duplikat jika ClOrdID yang sama dikirim ulang.
- Toleransi Status IN_DOUBT: Jangan ubah status order menjadi
FAILEDsaat circuit breaker trip akibat read timeout. Tandai transaksi sebagaiPENDING_CONFIRMATIONatauIN_DOUBT. - Distributed Outbox & Polling Recovery: Buat worker asinkron terpisah untuk memvalidasi status order ke SPI broker secara terjadwal menggunakan order query endpoint sebelum dana nasabah di-refund.
Postmortem: Penanganan Order Tertahan (In-Doubt Orders)
Jika degradasi SPI memicu circuit breaker terbuka secara masif dan sejumlah order tertahan, terapkan alur rekonsiliasi deterministik:
- Freeze Balance Sync: Kunci penarikan saldo dan trade lanjutan untuk akun yang memiliki status order
IN_DOUBT. - Broker Reconciliation Log: Unduh drop-copy atau trade logs akhir hari (EOD) dari broker SPI untuk mencocokkan status ClOrdID.
- Compensating Transaction: Jika broker memproses order tetapi sistem lokal mencatat timeout, buat entri jurnal manual (compensating transaction) untuk menyelaraskan kepemilikan lot saham dan saldo kas.
- Postmortem Root-Cause: Evaluasi apakah circuit breaker trip akibat network boundary (misal routing peering lintas negara) atau rate limit SPI. Sesuaikan
IntervaldanMaxRequestspada half-open state agar recovery berlangsung gradual.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!