Tantangan Migrasi: Dari Single-Threaded Actor ke Centralized Storage
Stateful edge actor seperti Cloudflare Durable Objects menjamin model eksekusi single-threaded dengan konsistensi kuat per ID actor secara lokal. Ketika beban kerja dipindahkan ke database terpusat (seperti PostgreSQL dan Redis), model konkurensi berubah drastis. Database relasional membutuhkan isolasi transaksi eksplisit, penanganan koneksi terdistribusi, serta penguncian yang ketat untuk mencegah anomali state.
Risiko terbesar dalam migrasi ini mencakup modifikasi paralel tak terkendali, kegagalan mutasi parsial akibat network partition, dan split-brain saat traffic dialihkan bertahap antara edge runtime dan backend terpusat. Untuk menjamin integritas data, strategi uji regresi harus memvalidasi transisi state secara matematis melalui idempotency, pengujian race condition, dan pipeline continuous integration (CI) otomatis.
Pencegahan Race Condition dan Anomali Split-Brain
Saat fase canary rollout atau dual-write, instance edge actor dan backend PostgreSQL berpotensi mengeksekusi operasi pada entitas yang sama secara bersamaan. Tanpa proteksi konkurensi, anomali lost update atau mutasi out-of-order pasti terjadi.
1. Optimistic Concurrency Control (OCC) dengan Versioning
Gunakan nomor versi inkremental monotonik pada setiap entitas. Jika transaksi mencoba memperbarui state dengan versi yang tidak sesuai dengan database snapshot, operasi dibatalkan seketika.
-- Skema tabel untuk mencegah overwrite konkuren
CREATE TABLE account_states (
id UUID PRIMARY KEY,
balance BIGINT NOT NULL,
version INT NOT NULL DEFAULT 1,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Eksekusi mutasi dengan verifikasi versi
UPDATE account_states
SET balance = balance + 500,
version = version + 1,
updated_at = NOW()
WHERE id = 'a3b8c2d1-0000-0000-0000-000000000000'
AND version = 3;Jika jumlah baris terpengaruh bernilai 0, aplikasi mendeteksi adanya race condition dan melempar error konflik untuk dievaluasi ulang oleh client.
2. Distributed Locking dengan Redis Redlock
Untuk proses multi-tahap yang membutuhkan isolasi di luar batas transaksi PostgreSQL tunggal, terapkan mutual exclusion menggunakan Redis sebelum eksekusi dimulai:
// Pseudocode penguncian state pada migrasi gateway
async function mutateWithLock(key, ttlMs, mutationFn) {
const token = crypto.randomUUID();
const acquired = await redis.set(`lock:${key}`, token, 'NX', 'PX', ttlMs);
if (!acquired) {
throw new Error('CONCURRENT_MUTATION_DETECTED');
}
try {
return await mutationFn();
} finally {
// Hapus lock hanya jika token identik (menggunakan skrip Lua)
const releaseScript = `
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
`;
await redis.eval(releaseScript, 1, `lock:${key}`, token);
}
}Pengujian Idempotency: Verifikasi Mutasi Jaringan Tak Terduga
Retry otomatis akibat transient network failure pada edge gateway berisiko menduplikasi mutasi balance atau order creation. Setiap endpoint stateful wajib menerapkan Idempotency-Key yang dicatat atomic di database terpusat.
Skenario uji idempotency mensimulasikan pemanggilan request paralel dengan payload identik menggunakan key yang sama:
import assert from 'node:assert';
import test from 'node:test';
test('Uji Konsistensi Idempotency pada Mutasi Saldo', async () => {
const idempotencyKey = 'req_idemp_' + crypto.randomUUID();
const payload = { accountId: 'acc-101', amount: 1000 };
// Eksekusi dua request identik secara simultan
const [resA, resB] = await Promise.all([
sendTransferRequest(idempotencyKey, payload),
sendTransferRequest(idempotencyKey, payload)
]);
// Hanya satu operasi yang boleh bernilai 201 (Created), lainnya 200/409 idempotent cache
const statuses = [resA.status, resB.status].sort();
assert.deepStrictEqual(statuses, [200, 201]);
assert.strictEqual(resA.body.finalBalance, resB.body.finalBalance);
// Validasi ke database: saldo hanya bertambah satu kali
const actualDbBalance = await getBalanceFromPg('acc-101');
assert.strictEqual(actualDbBalance, 1000);
});Otomatisasi Regression Test Suite di Pipeline CI
Uji konkurensi tidak boleh bergantung pada shared staging environment yang non-deterministik. Jalankan instance PostgreSQL dan Redis menggunakan GitHub Actions Services agar pengujian deterministik dan terisolasi.
name: Edge Migration Regression Suite
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
consistency-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: migration_test
POSTGRES_PASSWORD: secret
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 2s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 2s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run db:migrate
env:
DATABASE_URL: postgres://postgres:secret@localhost:5432/migration_test
- name: Run Chaos & Concurrency Regression Tests
run: npm run test:regression
env:
DATABASE_URL: postgres://postgres:secret@localhost:5432/migration_test
REDIS_URL: redis://localhost:6379
CHAOS_CONCURRENCY_LEVEL: 50Checklist Migrasi Stateful Production
- Terapkan Write Fencing: Saat cutover, nonaktifkan instance edge actor menggunakan tombstone record sebelum membuka akses tulis ke backend terpusat.
- Validasi Shadow Traffic: Jalankan mode shadow-write di mana edge actor tetap menjadi primary, sementara backend PostgreSQL menerima traffic sekunder untuk verifikasi kesesuaian output.
- Monitor Drift State: Pasang background reconciliation cron job untuk membandingkan checksum hash state antara storage Durable Objects dan PostgreSQL selama masa transisi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!