Worker & SQS — transaksi write yang tidak blocking
Sekarang giliran menjawab pertanyaan yang sengaja digantung di Fase 3: kalau endpoint API tidak boleh menunggu transaksi selesai secara sinkron, lalu bagaimana strukturnya?
Intisari
- Pola standarnya: API menerima request, mengirim job ke SQS, langsung membalas ke klien dengan status "diproses" — tidak menunggu konfirmasi block sama sekali.
- Worker terpisah (ECS task atau Lambda) mengambil job dari antrean, memanggil KMS untuk signing (materi sebelumnya), mengirim transaksi, lalu memantau konfirmasinya.
- Visibility timeout SQS harus disesuaikan lebih panjang dari waktu tunggu konfirmasi block — kalau tidak, job yang sama bisa diambil worker lain sebelum yang pertama selesai, berisiko mengirim transaksi dua kali.
- Dead-letter queue (DLQ) menampung job yang gagal berulang kali — supaya kegagalan tidak diam-diam hilang atau membanjiri antrean utama dengan retry tanpa henti.
- Nonce (Fase 0) harus dikelola terpusat oleh satu worker/proses per akun signer — kalau dua worker mengirim transaksi dari akun yang sama secara paralel tanpa koordinasi, salah satunya pasti gagal karena nonce bentrok.
Alur lengkap: dari request HTTP sampai transaksi terkonfirmasi
1. Klien: POST /api/gold/mint {ke, jumlah, buktiBrankas}
2. API: validasi, simpan job ke tabel `mint_jobs` (status: PENDING), kirim ke SQS
3. API: balas 202 Accepted { jobId } — TIDAK menunggu blockchain sama sekali
4. Worker: ambil job dari SQS
5. Worker: ambil nonce berikutnya (terkelola terpusat, lihat di bawah)
6. Worker: minta KMS menandatangani transaksi (materi sebelumnya)
7. Worker: eth_sendRawTransaction, simpan tx_hash, update status: SUBMITTED
8. Worker: polling eth_getTransactionReceipt sampai cukup konfirmasi
9. Worker: update status: CONFIRMED atau FAILED, hapus job dari SQS
Kenapa visibility timeout harus lebih panjang dari waktu konfirmasi
SQS menyembunyikan job dari antrean selama visibility timeout setelah diambil worker — supaya worker lain tidak mengambil job yang sama secara bersamaan. Tapi kalau timeout ini lebih pendek dari waktu yang dibutuhkan transaksi untuk terkonfirmasi, job itu akan terlihat lagi di antrean sebelum worker pertama selesai memprosesnya — worker kedua bisa mengambilnya dan mengirim transaksi yang sama dua kali.
Contoh yang SALAH:
Visibility timeout: 30 detik
Waktu tunggu konfirmasi block: bisa lebih dari 1 menit saat jaringan padat
→ job "terlihat lagi" sebelum worker pertama selesai → double-send
Contoh yang BENAR:
Visibility timeout: 5 menit, DIPERPANJANG worker (ChangeMessageVisibility)
setiap kali masih menunggu konfirmasi, dihapus baru setelah CONFIRMED/FAILED
Nonce: satu sumber kebenaran, bukan per-worker
Ini konsekuensi langsung dari Fase 0: nonce urut per akun, bukan per proses. Kalau kamu
menjalankan beberapa instance worker paralel (skalabilitas ECS Fargate) yang semuanya mengirim transaksi
dari akun signer yang sama, mereka tidak boleh masing-masing menghitung nonce sendiri-sendiri
dari eth_getTransactionCount — dua worker bisa membaca nonce yang sama di saat bersamaan dan
bentrok. Pola yang aman: satu tabel nonce_tracker di RDS dengan lock/increment atomik
(SELECT ... FOR UPDATE), atau desain satu logical signer per worker (masing-masing worker
punya key sendiri di KMS) supaya tidak ada kontensi nonce sama sekali.
Contoh worker sederhana (web3j + SQS)
while (true) {
List<Message> jobs = sqsClient.receiveMessage(receiveRequest).messages();
for (Message job : jobs) {
MintJob data = parseJob(job.body());
try {
BigInteger nonce = ambilNonceTerpusat(alamatSigner); // lock atomik di DB
RawTransaction tx = rakitTransaksi(data, nonce);
byte[] signed = tandaTanganLewatKMS(tx); // materi sebelumnya
EthSendTransaction hasil = web3j.ethSendRawTransaction(
Numeric.toHexString(signed)
).send();
simpanStatus(data.jobId, "SUBMITTED", hasil.getTransactionHash());
sqsClient.deleteMessage(job.receiptHandle());
} catch (Exception e) {
log.error("Job {} gagal, akan retry atau masuk DLQ", data.jobId, e);
// JANGAN delete — biarkan SQS retry sesuai kebijakan redrive
}
}
}
Dead-letter queue: jangan biarkan kegagalan hilang diam-diam
aws sqs set-queue-attributes \
--queue-url $QUEUE_URL \
--attributes '{
"RedrivePolicy": "{\"deadLetterTargetArn\":\"'$DLQ_ARN'\",\"maxReceiveCount\":\"5\"}"
}'
Job yang gagal diproses 5 kali berturut-turut otomatis pindah ke DLQ — bukan mengantre ulang tanpa henti (membebani sistem) atau hilang begitu saja (kehilangan jejak audit). Tim operasional memantau DLQ secara terpisah, biasanya dengan alarm CloudWatch, untuk investigasi manual.
Latihan: buat satu antrean SQS dengan DLQ terpasang (maxReceiveCount: 5). Tulis
skrip worker sederhana yang mengambil job berisi {ke, jumlah}, sengaja membuatnya gagal
terus-menerus (mis. dengan address tujuan yang tidak valid), dan verifikasi job itu benar-benar berpindah
ke DLQ setelah percobaan kelima — bukan mengantre selamanya di antrean utama.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.