Penomoran Faktur Aman: Hindari Duplikat dan Nomor yang Terlewat
Pelajari pola praktis untuk penomoran faktur yang aman terhadap konkurensi sehingga banyak pengguna dapat membuat faktur atau tiket tanpa duplikat atau kelompongan tak terduga.

Apa yang salah ketika dua orang membuat rekaman sekaligus
Bayangkan kantor yang sibuk jam 16:55. Dua orang selesai mengisi faktur dan menekan Simpan dalam rentang satu detik. Kedua layar sebentar menunjukkan “Faktur #1042”. Satu rekaman menang, yang lain gagal, atau lebih buruk, keduanya tersimpan dengan nomor yang sama. Ini adalah gejala dunia nyata yang paling umum: nomor duplikat yang muncul hanya di bawah beban.
Tiket mengalami hal yang sama. Dua agen membuat tiket baru untuk pelanggan yang sama pada waktu yang sama, dan sistem Anda mencoba “mengambil nomor berikutnya” dengan melihat rekaman terbaru. Jika kedua permintaan membaca nilai “terbaru” yang sama sebelum salah satu menulis, keduanya bisa memilih nomor berikut yang sama.
Gejala kedua lebih halus: angka yang terlewat. Anda mungkin melihat #1042, lalu #1044, sementara #1043 hilang. Ini sering terjadi setelah error atau retry. Satu permintaan memesan nomor, lalu penyimpanan gagal karena error validasi, timeout, atau pengguna menutup tab. Atau job background melakukan retry setelah gangguan jaringan dan mengambil nomor baru meski upaya pertama sudah mengonsumsi satu.
Untuk faktur, ini penting karena penomoran adalah bagian dari jejak audit Anda. Akuntan mengharapkan setiap faktur diidentifikasi secara unik, dan pelanggan dapat merujuk nomor faktur dalam pembayaran atau email dukungan. Untuk tiket, nomor adalah referensi yang digunakan dalam percakapan, laporan, dan ekspor. Duplikat menimbulkan kebingungan. Nomor yang hilang dapat memicu pertanyaan saat review, bahkan jika tidak ada kecurangan.
Kunci yang harus ditetapkan sejak awal: tidak setiap metode penomoran bisa sekaligus aman terhadap konkurensi dan tanpa kelompongan. Penomoran faktur yang aman terhadap konkurensi (tanpa duplikat, meski banyak pengguna) bisa dicapai dan seharusnya tidak dapat ditawar. Penomoran tanpa kelompongan juga mungkin, tapi butuh aturan tambahan dan sering mengubah cara Anda menangani draft, kegagalan, dan pembatalan.
Cara bagus untuk membingkai masalah adalah menanyakan apa yang harus dijamin oleh nomor Anda:
- Tidak boleh pernah berulang (unik, selalu)
- Sebaiknya sebagian besar meningkat (bagus untuk ditampilkan)
- Tidak boleh pernah terlewat (hanya jika Anda merancang demikian)
Setelah Anda memilih aturan, solusi teknis menjadi lebih mudah dipilih.
Mengapa duplikat dan kelompongan terjadi
Sebagian besar aplikasi mengikuti pola sederhana: pengguna klik Simpan, aplikasi minta nomor faktur atau tiket berikutnya, lalu memasukkan rekaman baru dengan nomor itu. Terlihat aman karena bekerja sempurna saat hanya satu orang.
Masalah muncul ketika dua penyimpanan terjadi hampir bersamaan. Kedua permintaan bisa mencapai langkah “ambil nomor berikutnya” sebelum salah satu menyelesaikan insert. Jika kedua pembacaan melihat nilai "berikutnya" yang sama, keduanya mencoba menulis nomor yang sama. Itu adalah kondisi balapan: hasil bergantung pada waktu, bukan logika.
Garis waktu tipikal seperti ini:
- Permintaan A membaca nomor berikut: 1042
- Permintaan B membaca nomor berikut: 1042
- Permintaan A memasukkan faktur 1042
- Permintaan B memasukkan faktur 1042 (atau gagal jika aturan unik memblokirnya)
Duplikat terjadi ketika tidak ada yang menghentikan insert kedua di database. Jika Anda hanya memeriksa “apakah nomor ini sudah terpakai?” di kode aplikasi, Anda masih bisa kalah oleh perlombaan antara pengecekan dan insert.
Kelompongan adalah masalah berbeda. Terjadi ketika sistem “memesan” nomor, tetapi rekaman tidak pernah menjadi faktur atau tiket yang sah. Penyebab umum: pembayaran gagal, error validasi yang terlambat, timeout, atau pengguna menutup tab setelah nomor diberikan. Bahkan jika insert gagal dan tidak ada yang tersimpan, nomor mungkin sudah terpakai.
Konkurensi tersembunyi memperburuk ini karena jarang hanya “dua manusia klik Simpan.” Anda mungkin juga memiliki:
- Klien API membuat rekaman paralel
- Impor yang dijalankan dalam batch
- Job background menghasilkan faktur di malam hari
- Retry dari aplikasi mobile dengan koneksi tidak stabil
Jadi akar permasalahan adalah: (1) konflik waktu ketika banyak permintaan membaca nilai counter yang sama, dan (2) nomor dialokasikan sebelum yakin transaksi akan berhasil. Setiap rencana untuk penomoran aman terhadap konkurensi harus memutuskan hasil mana yang bisa Anda toleransi: tanpa duplikat, tanpa kelompongan, atau keduanya, dan dalam kejadian apa saja (draft, retry, pembatalan).
Putuskan aturan penomoran sebelum memilih solusi
Sebelum merancang penomoran yang aman terhadap konkurensi, tuliskan apa arti nomor itu dalam bisnis Anda. Kesalahan umum adalah memilih metode teknis terlebih dahulu, lalu menemukan aturan akuntansi atau hukum mengharapkan sesuatu yang berbeda.
Mulailah dengan memisahkan dua tujuan yang sering tercampur:
- Unik: tidak ada dua faktur atau tiket yang pernah memiliki nomor yang sama.
- Tanpa kelompongan: nomor unik dan juga berurutan ketat (tidak ada yang terlewat).
Banyak sistem nyata mengincar hanya unik dan menerima kelompongan. Kelompongan bisa terjadi karena alasan normal: pengguna membuka draft lalu meninggalkannya, pembayaran gagal setelah nomor dipesan, atau rekaman dibuat lalu dibatalkan. Untuk tiket helpdesk, kelompongan biasanya tidak masalah sama sekali. Bahkan untuk faktur, banyak tim menerima kelompongan jika dapat menjelaskannya dengan jejak audit (voided, canceled, test, dll.). Penomoran tanpa kelompongan mungkin, tetapi memaksa aturan tambahan dan sering menambah friksi.
Selanjutnya, putuskan cakupan counter. Perbedaan kata kecil mengubah desain banyak:
- Satu sequence global untuk semuanya, atau sequence terpisah per perusahaan/tenant?
- Reset setiap tahun (2026-000123) atau tidak pernah reset?
- Seri berbeda untuk faktur vs nota kredit vs tiket?
- Perlu format yang ramah manusia (prefix, pemisah), atau cukup nomor internal?
Contoh konkret: produk SaaS dengan banyak perusahaan klien mungkin memerlukan nomor faktur yang unik per perusahaan dan reset per tahun, sementara tiket unik secara global dan tidak pernah reset. Itu dua counter berbeda dengan aturan berbeda, meski UI terlihat serupa.
Jika Anda benar-benar butuh tanpa kelompongan, jelaskan dengan tegas event apa yang diizinkan setelah nomor ditetapkan. Misalnya, apakah faktur boleh dihapus, atau hanya dibatalkan? Bisakah pengguna menyimpan draft tanpa nomor dan menetapkan nomor hanya saat final approval? Pilihan ini sering lebih penting daripada teknik database.
Tuliskan aturan singkat sebelum membangun:
- Jenis rekaman apa yang memakai sequence?
- Kapan sebuah nomor dianggap “dipakai” (draft, dikirim, dibayar)?
- Apa cakupan (global, per perusahaan, per tahun, per series)?
- Bagaimana menangani void dan koreksi?
Di AppMaster, aturan semacam ini ditempatkan berdekatan dengan model data dan alur proses bisnis, sehingga tim menerapkan perilaku yang sama di mana-mana (API, web UI, dan mobile) tanpa kejutan.
Pendekatan umum dan apa yang dijamin masing-masing
Ketika orang berbicara tentang “penomoran faktur,” mereka sering mencampur dua tujuan berbeda: (1) tidak pernah menghasilkan nomor yang sama dua kali, dan (2) tidak pernah ada celah. Sebagian besar sistem mudah menjamin yang pertama. Yang kedua jauh lebih sulit, karena celah dapat muncul kapan saja sebuah transaksi gagal, draft ditinggalkan, atau rekaman dibatalkan.
Pendekatan 1: Sequence database (unik dan cepat)
Sequence PostgreSQL adalah cara paling sederhana untuk mendapatkan nomor unik yang meningkat di bawah beban. Ia skala dengan baik karena database dibuat untuk memberikan nilai sequence dengan cepat, bahkan ketika banyak pengguna membuat rekaman sekaligus.
Yang Anda dapatkan: keunikan dan urutan (kebanyakan meningkat). Yang tidak Anda dapatkan: tanpa kelompongan. Jika insert gagal setelah nomor diberikan, nomor itu “terbakar,” dan Anda akan melihat celah.
Pendekatan 2: Constraint unik + retry (biarkan database memutuskan)
Di sini Anda menghasilkan kandidat nomor (dari logika aplikasi), menyimpannya, dan mengandalkan constraint UNIQUE untuk menolak duplikat. Jika terjadi konflik, Anda retry dengan nomor baru.
Ini bisa bekerja, tapi cenderung menjadi berisik di bawah konkurensi tinggi. Anda bisa berakhir dengan lebih banyak retry, lebih banyak transaksi gagal, dan lonjakan yang sulit di-debug. Ini juga tidak menjamin tanpa kelompongan kecuali digabungkan dengan aturan reservasi ketat, yang menambah kompleksitas.
Pendekatan 3: Baris counter dengan locking (mendekati tanpa kelompongan)
Jika Anda benar-benar membutuhkan nomor tanpa kelompongan, pola umum adalah tabel counter khusus (satu baris per cakupan penomoran, mis. per tahun atau per perusahaan). Anda mengunci baris itu dalam transaksi, menambahkannya, dan menggunakan nilai baru.
Ini yang paling mendekati tanpa kelompongan dalam desain database normal, tapi ada biayanya: ini menciptakan satu titik “hot spot” yang harus ditunggu oleh semua penulis. Ini juga menaikkan risiko operasional (transaksi lama, timeout, deadlock).
Pendekatan 4: Layanan reservasi terpisah (khusus)
“Layanan penomoran” terpisah bisa memusatkan aturan di beberapa aplikasi atau database. Biasanya hanya layak ketika Anda memiliki beberapa sistem yang menerbitkan nomor dan tidak bisa mengonsolidasikan penulisan.
Tradeoff-nya adalah risiko operasional: Anda menambah layanan lain yang harus benar, highly available, dan konsisten.
Ringkasnya:
- Sequence: unik, cepat, menerima celah
- Unik + retry: unik, sederhana pada beban rendah, bisa thrash pada beban tinggi
- Baris counter ter-lock: bisa tanpa kelompongan, lebih lambat di bawah konkurensi berat
- Layanan terpisah: fleksibel lintas sistem, kompleksitas dan mode kegagalan tertinggi
Jika Anda membangun ini di tool no-code seperti AppMaster, pilihan yang sama masih berlaku: kebenaran ada di database. Logika aplikasi bisa membantu dengan retry dan pesan error yang jelas, tapi jaminan akhir seharusnya datang dari constraint dan transaksi.
Langkah demi langkah: mencegah duplikat dengan sequence dan constraint unik
Jika tujuan utama Anda mencegah duplikat (bukan menjamin tanpa kelompongan), pola paling sederhana dan andal adalah: biarkan database menghasilkan ID internal, dan paksa keunikan pada nomor yang ditampilkan ke pelanggan.
Mulai dengan memisahkan dua konsep. Gunakan nilai yang dihasilkan database (identity/sequence) sebagai primary key untuk join, edit, dan ekspor. Simpan invoice_no atau ticket_no sebagai kolom terpisah yang ditampilkan ke orang.
Setup praktis di PostgreSQL
Berikut pendekatan PostgreSQL umum yang menjaga logika “nomor berikutnya” di dalam database, di mana konkurensi ditangani dengan benar.
-- Internal, never-shown primary key
create table invoices (
id bigint generated always as identity primary key,
invoice_no text not null,
created_at timestamptz not null default now()
);
-- Business-facing uniqueness guarantee
create unique index invoices_invoice_no_uniq on invoices (invoice_no);
-- Sequence for the visible number
create sequence invoice_no_seq;
Sekarang buat nomor tampilan pada saat insert (bukan dengan melakukan "select max(invoice_no) + 1"). Satu pola sederhana adalah memformat nilai sequence di dalam INSERT:
insert into invoices (invoice_no)
values (
'INV-' || lpad(nextval('invoice_no_seq')::text, 8, '0')
)
returning id, invoice_no;
Bahkan jika 50 pengguna klik “Create invoice” sekaligus, setiap insert mendapat nilai sequence berbeda, dan index unik memblokir duplikat yang tidak disengaja.
Apa yang dilakukan saat terjadi tabrakan
Dengan sequence biasa, tabrakan jarang. Biasanya terjadi ketika Anda menambahkan aturan tambahan seperti “reset per tahun”, “per tenant”, atau nomor yang dapat diedit pengguna. Itu sebabnya constraint unik masih penting.
Di level aplikasi, tangani error pelanggaran unik dengan loop retry kecil. Buat sederhana dan terbatas:
- Coba insert
- Jika mendapatkan error unique constraint pada invoice_no, coba lagi
- Hentikan setelah sejumlah percobaan kecil dan tampilkan error yang jelas
Ini bekerja baik karena retry hanya terpicu ketika sesuatu yang tidak biasa terjadi, seperti dua jalur kode menghasilkan nomor terformat yang sama.
Perkecil window balapan
Jangan hitung nomor di UI, dan jangan “membooking” nomor dengan membaca dulu lalu insert kemudian. Hasilkan nomor sedekat mungkin dengan penulisan ke database.
Jika Anda menggunakan AppMaster dengan PostgreSQL, Anda bisa memodelkan id sebagai identity primary key di Data Designer, menambahkan constraint unik untuk invoice_no, dan menghasilkan invoice_no selama alur create sehingga terjadi bersamaan dengan insert. Dengan begitu database tetap sumber kebenaran, dan masalah konkurensi tertangani di tempat PostgreSQL paling kuat.
Langkah demi langkah: bangun counter tanpa kelompongan dengan locking baris
Jika Anda benar-benar butuh nomor tanpa kelompongan (tidak ada nomor faktur atau tiket yang hilang), Anda bisa menggunakan tabel counter transaksional dan locking baris. Idenya sederhana: hanya satu transaksi pada satu waktu yang boleh mengambil nomor berikutnya untuk suatu cakupan, sehingga nomor dibagikan berurutan.
Pertama, putuskan cakupan Anda. Banyak tim membutuhkan sequence terpisah per perusahaan, per tahun, atau per series (mis. INV vs CRN). Tabel counter menyimpan nomor terakhir yang dipakai untuk setiap cakupan.
Berikut pola praktis untuk penomoran aman terhadap konkurensi menggunakan row locks PostgreSQL:
- Buat tabel, misalnya
number_counters, dengan kolom seperticompany_id,year,series,last_number, dan unique key pada(company_id, year, series). - Mulai transaksi database.
- Lock baris counter untuk cakupan Anda menggunakan
SELECT last_number FROM number_counters WHERE ... FOR UPDATE. - Hitung
next_number = last_number + 1, update baris counter menjadilast_number = next_number. - Insert baris invoice atau ticket menggunakan
next_number, lalu commit.
Kunci utamanya adalah FOR UPDATE. Di bawah beban, Anda tidak mendapatkan duplikat. Anda juga tidak mendapatkan kelompongan dari "dua pengguna mendapat nomor yang sama", karena transaksi kedua tidak bisa membaca dan menambah baris counter yang sama sampai transaksi pertama commit (atau rollback). Sebagai gantinya, permintaan kedua menunggu sebentar. Menunggu ini adalah harga yang dibayar untuk tanpa kelompongan.
Menginisialisasi cakupan baru
Anda juga perlu rencana untuk pertama kali sebuah cakupan muncul (perusahaan baru, tahun baru, series baru). Dua opsi umum:
- Buat baris counter sebelumnya (mis. buat baris untuk tahun depan di Desember).
- Buat on demand: coba insert baris counter dengan
last_number = 0, dan jika sudah ada, fallback ke alur lock-and-increment biasa.
Jika Anda membangun ini di tool no-code seperti AppMaster, jaga seluruh urutan “lock, increment, insert” di dalam satu transaksi di logika bisnis Anda, sehingga semua itu terjadi atau tidak sama sekali.
Kasus tepi: draft, simpan gagal, pembatalan, dan edit
Sebagian besar bug penomoran muncul di bagian berantakan: draft yang tidak pernah diposting, simpan yang gagal, faktur yang dibatalkan, dan rekaman yang diedit setelah seseorang melihat nomor. Jika Anda ingin penomoran yang aman terhadap konkurensi, Anda butuh aturan jelas kapan nomor menjadi “nyata”.
Keputusan terbesar adalah waktu penetapan nomor. Jika Anda menetapkan nomor saat seseorang mengklik “New invoice”, Anda akan mendapat celah dari draft yang ditinggalkan. Jika Anda menetapkan hanya saat faktur difinalkan (posted, issued, sent, atau apa pun artinya “final” di bisnis Anda), Anda bisa menjaga nomor lebih rapat dan mudah dijelaskan.
Simpan gagal dan rollback adalah tempat ekspektasi sering bertabrakan dengan perilaku database. Dengan sequence biasa, setelah nomor diambil, nomor itu dianggap terpakai meski transaksi nanti gagal. Itu normal dan aman, tapi bisa menciptakan celah. Jika kebijakan Anda mengharuskan tanpa kelompongan, nomor harus ditetapkan hanya pada langkah final dan hanya jika transaksi commit. Itu biasanya berarti mengunci satu baris counter, menulis nomor final, dan commit sebagai satu unit. Jika ada langkah gagal, tidak ada nomor yang ditetapkan.
Pembatalan dan void hampir tidak boleh “menggunakan kembali” nomor. Pertahankan nomor dan ubah status. Auditor dan pelanggan mengharapkan histori tetap konsisten, bahkan ketika dokumen dikoreksi.
Edit lebih sederhana: setelah nomor terlihat di luar sistem, anggap itu permanen. Jangan pernah mengganti nomor faktur atau tiket setelah dibagikan, diekspor, atau dicetak. Jika Anda butuh koreksi, buat dokumen baru dan referensikan yang lama (mis. nota kredit atau tiket pengganti), jangan ubah sejarah.
Aturan praktis yang banyak diadopsi:
- Draft tidak punya nomor final (gunakan ID internal atau “DRAFT”).
- Tetapkan nomor hanya pada langkah “Post/Issue”, di dalam transaksi yang sama dengan perubahan status.
- Voids dan pembatalan menjaga nomor, tapi berikan status dan alasan yang jelas.
- Nomor yang dicetak/email tidak berubah.
- Impor mempertahankan nomor asli dan mengatur counter agar mulai setelah nilai maksimum yang diimpor.
Migrasi dan impor butuh perhatian khusus. Jika Anda pindah dari sistem lain, bawa nomor faktur yang ada apa adanya, lalu atur counter mulai setelah nilai maksimum yang diimpor. Juga putuskan bagaimana menangani format konflik (mis. prefix berbeda per tahun). Biasanya lebih baik menyimpan “nomor tampilan” persis seperti semula, dan menyimpan primary key internal terpisah.
Contoh: helpdesk membuat tiket cepat, tapi banyak yang berupa draft. Tetapkan nomor tiket hanya saat agen klik “Kirim ke pelanggan.” Itu menghindari pemborosan nomor pada draft yang ditinggalkan, dan menjaga sequence tampilan selaras dengan komunikasi pelanggan nyata. Di tool no-code seperti AppMaster, ide yang sama berlaku: simpan draft sebagai rekaman tanpa nomor publik, lalu hasilkan nomor final saat langkah bisnis “submit” yang melakukan commit sukses.
Kesalahan umum yang menyebabkan duplikat atau kelompongan mengejutkan
Sebagian besar masalah penomoran berasal dari satu ide: memperlakukan nomor sebagai nilai tampilan bukan state bersama. Ketika beberapa orang menyimpan sekaligus, sistem butuh satu tempat jelas untuk memutuskan nomor berikutnya, dan satu aturan jelas untuk apa yang terjadi saat gagal.
Kesalahan klasik adalah menggunakan SELECT MAX(number) + 1 di kode aplikasi. Terlihat baik saat pengujian satu pengguna, tapi dua permintaan bisa membaca MAX yang sama sebelum salah satu commit. Keduanya menghasilkan nilai berikut yang sama, dan Anda mendapatkan duplikat. Bahkan jika Anda menambahkan “cek lalu retry,” Anda masih bisa membuat beban ekstra dan lonjakan aneh saat traffic puncak.
Sumber duplikat lain adalah menghasilkan nomor di sisi klien (browser atau mobile) sebelum menyimpan. Klien tidak tahu apa yang dilakukan pengguna lain, dan tidak bisa dengan aman memesan nomor jika penyimpanan gagal. Nomor yang dibuat klien bisa dipakai untuk label sementara seperti “Draft 12,” tapi tidak untuk ID resmi faktur atau tiket.
Kelompongan mengejutkan tim yang mengira sequence itu tanpa kelompongan. Di PostgreSQL, sequence dirancang untuk keunikan, bukan kontinuitas sempurna. Nomor bisa dilewati saat transaksi rollback, saat Anda memprefetch ID, atau saat database restart. Itu perilaku normal. Jika kebutuhan nyata Anda adalah “tanpa duplikat,” sequence plus constraint unik biasanya jawaban yang tepat. Jika kebutuhan Anda benar-benar “tanpa kelompongan,” Anda perlu pola berbeda (biasanya row locking) dan harus menerima trade-off throughput.
Locking juga bisa gagal ketika terlalu luas. Satu lock global untuk semua penomoran memaksa setiap aksi create dalam antrean, padahal Anda bisa mempartisi counter berdasarkan perusahaan, lokasi, atau tipe dokumen. Itu bisa memperlambat keseluruhan sistem dan membuat pengguna merasa simpanan “acak” macet.
Periksa kesalahan umum berikut saat mengimplementasikan penomoran aman terhadap konkurensi:
- Menggunakan
MAX + 1(atau “cari nomor terakhir”) tanpa constraint unik di level database. - Menghasilkan nomor final di sisi klien, lalu mencoba “memperbaiki konflik nanti.”
- Mengira sequence PostgreSQL tanpa kelompongan, lalu memperlakukan celah sebagai error.
- Mengunci satu counter bersama untuk semua, bukan mempartisi counter bila masuk akal.
- Hanya menguji dengan satu pengguna, sehingga kondisi balapan baru muncul setelah peluncuran.
Tip tes praktis: jalankan tes konkurensi sederhana yang membuat 100 hingga 1.000 rekaman secara paralel lalu periksa duplikat dan celah tak terduga. Jika Anda membangun di tool no-code seperti AppMaster, aturan yang sama berlaku: pastikan nomor final ditetapkan di dalam satu transaksi sisi server, bukan di alur UI.
Pemeriksaan cepat sebelum rilis
Sebelum meluncurkan penomoran faktur atau tiket, lakukan pengecekan singkat pada bagian yang biasanya gagal di bawah traffic nyata. Tujuannya sederhana: setiap rekaman mendapat tepat satu nomor bisnis, dan aturan Anda tetap berlaku bahkan saat 50 orang klik "Create" sekaligus.
Checklist pre-ship praktis untuk penomoran aman terhadap konkurensi:
- Pastikan field nomor bisnis memiliki constraint unik di database (bukan hanya cek di UI). Ini adalah garis pertahanan terakhir jika dua permintaan bertabrakan.
- Pastikan nomor ditetapkan di dalam transaksi database yang sama yang menyimpan rekaman. Jika penetapan nomor dan penyimpanan terpisah antar permintaan, Anda pada akhirnya akan melihat duplikat.
- Jika Anda mengharuskan tanpa kelompongan, tetapkan nomor hanya saat rekaman difinalkan (mis. saat faktur diterbitkan, bukan saat draft dibuat). Draft, form yang ditinggalkan, dan pembayaran yang gagal adalah sumber kelompongan paling umum.
- Tambahkan strategi retry untuk konflik langka. Bahkan dengan locking baris atau sequence, Anda bisa terkena serialization error, deadlock, atau pelanggaran unik dalam kasus tepi. Retry sederhana dengan backoff pendek sering cukup.
- Lakukan stress test dengan 20 sampai 100 create simultan di semua titik masuk: UI, public API, dan bulk import. Uji kombinasi realistis seperti lonjakan, jaringan lambat, dan double submit.
Cara cepat memvalidasi setup: simulasikan momen helpdesk sibuk: dua agen membuka form "New ticket", satu submit dari web app sementara job impor memasukkan tiket dari inbox email pada waktu yang sama. Setelah run, periksa bahwa semua nomor unik, dalam format yang benar, dan kegagalan tidak meninggalkan rekaman setengah jadi.
Jika Anda membangun workflow di AppMaster, prinsip yang sama berlaku: jaga penetapan nomor di transaksi database, andalkan constraint PostgreSQL, dan uji tindakan UI serta endpoint API yang sama-sama membuat entitas. Banyak tim merasa aman dalam pengujian manual tapi terkejut pada hari pertama pengguna asli membludak.
Contoh: helpdesk sibuk dan langkah selanjutnya
Bayangkan desk support di mana agen membuat tiket sepanjang hari di web app, sementara integrasi juga membuat tiket dari chat tool dan email. Semua orang mengharapkan nomor tiket seperti T-2026-000123, dan masing-masing nomor menunjuk tepat ke satu tiket.
Pendekatan naif: baca “nomor tiket terakhir”, tambah 1, simpan tiket baru. Di bawah beban, dua permintaan bisa baca “nomor terakhir” yang sama sebelum salah satu menyimpan. Keduanya menghitung nomor berikut yang sama, dan Anda mendapat duplikat. Jika Anda mencoba “memperbaiki” dengan retry setelah gagal, seringkali Anda malah menciptakan kelompongan tanpa sengaja.
Database bisa menghentikan duplikat meski kode aplikasi naif. Tambahkan constraint unik pada kolom ticket_number. Saat dua permintaan mencoba nomor sama, satu insert gagal dan Anda dapat retry dengan bersih. Ini inti penomoran aman terhadap konkurensi juga: biarkan database menegakkan keunikan, bukan UI Anda.
Penomoran tanpa kelompongan mengubah alur kerja. Jika Anda memerlukan tanpa kelompongan, biasanya tidak bisa menetapkan nomor final saat tiket pertama dibuat (draft). Sebagai gantinya, buat tiket dengan status Draft dan ticket_number nullable. Tetapkan nomor hanya saat tiket difinalkan, sehingga simpan yang gagal dan draft yang ditinggalkan tidak “membakar” nomor.
Desain tabel sederhana terlihat seperti:
- tickets: id, created_at, status (Draft, Open, Closed), ticket_number (nullable), finalized_at
- ticket_counters: key (mis. "tickets_2026"), next_number
Di AppMaster, Anda dapat memodelkan ini di Data Designer dengan tipe PostgreSQL, lalu membangun logika di Business Process Editor:
- Create Ticket: insert ticket dengan status=Draft dan tanpa ticket_number
- Finalize Ticket: mulai transaksi, lock baris counter, set ticket_number, increment next_number, commit
- Test: jalankan dua aksi “Finalize” bersamaan dan pastikan tidak ada duplikat
Langkah selanjutnya: mulai dengan aturan Anda (unik saja vs benar-benar tanpa kelompongan). Jika Anda bisa menerima kelompongan, sequence database + constraint unik biasanya cukup dan menjaga alur sederhana. Jika Anda harus tanpa kelompongan, pindahkan penomoran ke langkah finalisasi dan perlakukan “draft” sebagai state kelas satu. Lalu lakukan load-test dengan banyak agen klik bersamaan dan integrasi API yang mengirim lonjakan, sehingga Anda melihat perilaku sebelum pengguna nyata melakukannya.


