gonik adalah pustaka (library) parser NIK (Nomor Induk Kependudukan) KTP Indonesia berkinerja tinggi (high-performance) yang ditulis menggunakan bahasa Go. Dirancang khusus untuk skenario industri yang membutuhkan kecepatan pemrosesan super kilat dengan efisiensi memori ekstrem murni Zero-Allocation (0 B/op, 0 allocs/op).
Library ini mampu mengurai dan memvalidasi lebih dari 5-6 juta data NIK per detik pada perangkat keras kelas standar berkat optimasi arsitektur memori di level compiler stack dan peniadaan pointer chasing.
Berbeda dengan library parser NIK konvensional atau versi porting dari bahasa dinamis (seperti PHP/Node.js) yang sering kali memicu alokasi memori berulang di heap, gonik memaksimalkan kapabilitas runtime Go melalui pendekatan:
-
Memory-Resident Preheated Map: Dataset wilayah se-Indonesia dimuat sekali di awal (startup) ke dalam RAM (
dbCache). Ini menjamin kompleksitas pencarian konstan$O(1)$ yang sangat cepat dibandingkan metode disk-backed binary search. -
Murni Zero-Allocation (
$0\text{ B/op}$ ): Konstruktor dan metode parser menggunakan Value Type (bukan pointer). Seluruh siklus hidup objek dikunci di dalam Stack Memory, menghilangkan ketergantungan pada Garbage Collector (GC) dan mencegah degradasi performa akibat cache miss. -
Matematika Kering: Mengonversi string tanggal lahir dan penentuan jenis kelamin secara langsung lewat kalkulasi numerik karakter indeks byte (
nik[i] - '0'), sepenuhnya menyingkirkan fungsi mahal sepertistrconv.Atoidan penanganan zona waktu lokal (time.Local). - Single-Pass Map Lookup: Memangkas frekuensi operasi hashing map wilayah dari yang awalnya 7 kali redundan menjadi maksimal 3 kali lookup sekuensial ter-cache di stack untuk menyusun informasi data KTP secara utuh.
Metode instansiasi read-only untuk mengekstrak informasi terstruktur dari string NIK.
| Metode | Jenis Return | Deskripsi |
|---|---|---|
New(nik string) Parser |
Parser |
Konstruktor Value Type untuk menginisialisasi objek parser di Stack. |
Province() string |
string |
Mendapatkan nama provinsi berdasarkan 2 digit pertama NIK. |
RegencyCity() string |
string |
Mendapatkan nama kabupaten/kota berdasarkan 4 digit pertama NIK. |
District() string |
string |
Mendapatkan nama kecamatan berdasarkan 6 digit pertama NIK. |
PostalCode() string |
string |
Mendapatkan kode pos yang melekat pada level kecamatan. |
Gender() string |
string |
Mendeteksi gender (Male / Female) dengan penanganan otomatis offset 40. |
BirthDate() time.Time |
time.Time |
Mengembalikan objek tanggal lahir tervalidasi. |
GetDetails() Details |
Details |
Mengonversi seluruh informasi NIK ke dalam satu struct tunggal Details. |
| Metode | Deskripsi |
|---|---|
GenerateNIK(dst []byte, kecamatanID string, birthDate time.Time, gender string, uniqueCode string) (string, error) |
Membuat 16 digit NIK tiruan tervalidasi dengan menulis hasil ke buffer yang sudah disediakan. |
GenerateRandomNIK(dst []byte) (string, error) |
Membuat 16 digit NIK acak tervalidasi dengan menulis hasil ke buffer yang sudah disediakan. |
package main
import (
"fmt"
"github.com/ballspins/gonik"
)
func main() {
// 1. Muat dataset wilayah ke memori sekali saja di awal aplikasi
if err := gonik.InitDatabase(); err != nil {
panic(err)
}
// 2. Instansiasi parser (Murni alokasi Stack)
parser := gonik.New("3578201503990001")
// 3. Ambil data parsial atau sekaligus
fmt.Println("District:", parser.District())
fmt.Println("Birth Date:", parser.BirthDate().Format("2006-01-02"))
// 4. Ambil seluruh detail terstruktur (Ukuran struct Details: 192 bytes)
details := parser.GetDetails()
if details.IsValid {
fmt.Printf("%+v\n", details)
}
}Berikut contoh pemanfaatan fungsi generator untuk membuat NIK tiruan secara efisien:
package main
import (
"fmt"
"time"
"github.com/ballspins/gonik"
)
func main() {
var buf [16]byte
birthDate := time.Date(1999, 3, 15, 0, 0, 0, 0, time.UTC)
nik, err := gonik.GenerateNIK(buf[:], "357820", birthDate, "pria", "0001")
if err != nil {
panic(err)
}
fmt.Println("Generated NIK:", nik)
}Parameter:
dst: buffer byte dengan panjang minimal 16 untuk menampung hasil NIKkecamatanID: kode kecamatan 6 digitbirthDate: tanggal lahir yang akan dikonversigender:priaatauwanitauniqueCode: kode unik 4 digit opsional
Berikut contoh pemanfaatan fungsi generator untuk membuat NIK secara acak dengan efisien:
package main
import (
"fmt"
"github.com/ballspins/gonik"
)
func main() {
var buf [16]byte
err := gonik.InitDatabase()
if err != nil {
panic(err)
}
nik, err := gonik.GenerateRandomNIK(buf[:])
if err != nil {
panic(err)
}
fmt.Println("Generated NIK:", nik)
}Parameter:
dst: buffer byte dengan panjang minimal 16 untuk menampung hasil NIK
Library gonik didesain dengan tanda tangan fungsi (function signature) menerima parameter buffer dari luar (dst []byte). Pendekatan ini sengaja diambil untuk memberikan kendali penuh kepada pengembang dalam mengatur siklus hidup memori dan menghindari tekanan berlebih pada Garbage Collector (GC).
Berikut adalah dua pola implementasi profesional untuk memanfaatkan efisiensi murni Zero-Allocation di berbagai skenario:
Jika Anda perlu menghasilkan jutaan data NIK acak secara berurutan (misalnya untuk kebutuhan database seeding atau pengujian beban), alokasikan buffer array sekali saja di luar perulangan.
Eksekusi berikutnya akan langsung menimpa (overwrite) memori pada indeks yang sama secara instan tanpa memicu alokasi heap baru.
package main
import (
"github.com/ballspins/gonik"
)
func main() {
_ = gonik.InitDatabase()
// 1. Alokasikan buffer 16 byte SEKALI SAJA di Stack (Luar Loop)
var buf [16]byte
for i := 0; i < 1000000; i++ {
// 2. Data lama di dalam buf otomatis tertimpa bersih (0 allocs/op)
nik, _ := gonik.GenerateRandomNIK(buf[:])
// Proses data nik Anda di sini...
_ = nik
}
}Kenapa ini efisien? RAM aplikasi Anda akan tetap stabil dan tidak akan naik sama sekali dari iterasi pertama hingga ke sejuta, karena tidak ada objek baru yang diciptakan di dalam lingkaran eksekusi.
Jika fungsi generator ditaruh di dalam skenario konkuren tinggi seperti server HTTP, performa terbaik dicapai dengan mengadopsi mekanisme daur ulang (recycle) memori memanfaatkan sync.Pool.
Mekanisme ini mencegah array memicu escape analysis ke memori Heap akibat pembuatan variabel lokal yang berulang di setiap goroutine masuk.
package main
import (
"fmt"
"net/http"
"sync"
"github.com/ballspins/gonik"
)
// Sediakan pool khusus buffer 16 byte
var bufferPool = sync.Pool{
New: func() any {
b := make([]byte, 16)
return &b // Menyimpan pointer ke slice untuk efisiensi pool
},
}
func handleGenerateNIK(w http.ResponseWriter, r *http.Request) {
// 1. Pinjam buffer dari pool pusat
bufPtr := bufferPool.Get().(*[]byte)
buf := *bufPtr
// 2. Tulis data NIK langsung ke buffer pinjaman
nik, err := gonik.GenerateRandomNIK(buf)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, "NIK:", nik)
// 3. Kembalikan buffer ke pool untuk digunakan request berikutnya
bufferPool.Put(bufPtr)
}Mengapa Buffer Tidak Perlu Dibersihkan (Zeroing Out)? Mungkin Anda bertanya-tanya mengapa kita tidak membersihkan isi buffer (mengisi ulang elemennya dengan angka 0) sebelum dikembalikan ke sync.Pool atau di dalam perulangan.
Pembersihan memori (zeroing out) hanya wajib dilakukan pada dua kondisi:
Keamanan Data Transaksional: Menangani data sensitif (seperti password plaintext, token JWT, atau kunci enkripsi) guna mencegah kebocoran data di lapisan memori lain.
Panjang Data Dinamis: Jika fungsi berikutnya menulis data dengan panjang bervariasi (misal menulis 5 byte di atas sisa memori lama sepanjang 11 byte, yang akan menghasilkan residu data rusak).
Karena struktur data NIK pasti tepat berukuran 16 byte, algoritma internal gonik dijamin selalu menimpa indeks koordinat 0 sampai 15 secara utuh dan sempurna. Tidak ada residu data lama yang akan bocor atau merusak hasil generation berikutnya.
Pengujian dilakukan secara objektif dengan membandingkan eksekusi ketat subsistem pengujian Go (go test -bench) antara library (fanchann/nik-parser) melawan gonik (ballspins/gonik) pada arsitektur mesin yang sama.
| Metrik Kinerja | fanchann/nik-parser | ballspins/gonik | Keunggulan gonik |
|---|---|---|---|
Kecepatan rata-rata (ns/op) |
~643.4 ns/op | ~222.4 ns/op | ~2.9x Lebih Cepat |
Alokasi Memori (B/op) |
210 B/op | 0 B/op | Mutlak (Zero Allocation) |
Jumlah Alokasi Heap (allocs/op) |
4 allocs/op | 0 allocs/op | Murni Bebas Sampah Heap |
| Throughput Data (per detik) | ~1.55 Juta NIK/detik | ~4.49 Juta NIK/detik | Memproses ~2.9 Juta Lebih Banyak |
- Peniadaan Alokasi Heap (0 B/op): Library fanchann/nik-parser menghasilkan sampah memori sebesar 210 byte dan memicu 4 kali operasi alokasi heap (
4 allocs/op) pada setiap satu kali proses eksekusi NIK.gonikmengunci seluruh siklus hidup objek di dalam Stack Memory sehingga CPU tidak perlu membuang siklus untuk berinteraksi dengan runtime allocator. - Bebas Degradasi Garbage Collector (GC): Akibat dari alokasi kumulatif fanchann/nik-parser, memproses 1 juta data secara berurutan akan memaksa sistem meminjam memori total hingga ~200 MB sebelum disapu oleh GC. Di sisi lain,
gonikmempertahankan penggunaan memori kumulatif yang stabil dan bersih sejak iterasi pertama hingga terakhir. - Single-Pass Map Lookup: Jika parser lain melakukan pencarian map berulang kali (redundant lookup) untuk mengambil data Provinsi, Kabupaten, dan Kecamatan secara terpisah,
gonikhanya melakukan maksimal 3 kali operasi hashing map sekuensial ter-cache untuk menyusun objek data KTP secara instan.
Berikut adalah hasil pengujian performa bawaan subsistem pengujian Go pada library gonik:
λ go test -bench=. -benchmem
Ukuran total struct Details: 192 bytes
goos: windows
goarch: amd64
pkg: github.com/ballspins/gonik
cpu: AMD Ryzen 3 7320U with Radeon Graphics
BenchmarkGenerateNIK_BatchLoop-8 20584990 54.25 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateRandomNIK_BatchLoop-8 6471228 184.5 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateNIK_SyncPool-8 16970053 70.99 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateRandomNIK_SyncPool-8 5950116 199.6 ns/op 0 B/op 0 allocs/op
BenchmarkNikParser_GetDetails-8 6629397 184.0 ns/op 0 B/op 0 allocs/op
BenchmarkParser_Province-8 56872306 22.51 ns/op 0 B/op 0 allocs/op
BenchmarkParser_RegencyCity-8 50162190 23.00 ns/op 0 B/op 0 allocs/op
BenchmarkParser_District-8 54330355 22.79 ns/op 0 B/op 0 allocs/op
BenchmarkParser_PostalCode-8 52199331 22.40 ns/op 0 B/op 0 allocs/op
BenchmarkParser_Gender-8 137527414 8.740 ns/op 0 B/op 0 allocs/op
BenchmarkParser_BirthDate-8 17812029 68.46 ns/op 0 B/op 0 allocs/op
BenchmarkParser_getSubstring-8 1000000000 0.4357 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/ballspins/gonik 15.581s
λ go run ./cmd/sampler
Running Benchmark sampling 50x
================ AVG SAMPLING RESULT ================
BenchmarkGenerateNIK_BatchLoop : 66.50 ns/op (50 samples)
BenchmarkGenerateRandomNIK_BatchLoop : 237.52 ns/op (50 samples)
BenchmarkGenerateNIK_SyncPool : 89.09 ns/op (50 samples)
BenchmarkGenerateRandomNIK_SyncPool : 257.32 ns/op (50 samples)
BenchmarkNikParser_GetDetails : 222.45 ns/op (50 samples)
BenchmarkParser_Province : 27.63 ns/op (50 samples)
BenchmarkParser_RegencyCity : 27.77 ns/op (50 samples)
BenchmarkParser_District : 24.05 ns/op (50 samples)
BenchmarkParser_PostalCode : 23.29 ns/op (50 samples)
BenchmarkParser_Gender : 9.09 ns/op (50 samples)
BenchmarkParser_BirthDate : 68.48 ns/op (50 samples)
BenchmarkParser_getSubstring : 0.46 ns/op (50 samples)
==========================================================
Total Sampling Time Execution: 13m35.818s
==========================================================Analisis Angka: Operasi komparasi tercepat dicatat oleh
getSubStringsebesar ~0.48 ns/op yang menandakan fungsi berhasil di-inline penuh oleh compiler ke tingkat register CPU. Kecepatan single-pass detail extraction (GetDetails) kokoh berada pada level ~222 ns/op murni tanpa alokasi heap tunggal pun (0 B/op).
Untuk menjaga akurasi konteks data di atas, berikut adalah spesifikasi mesin eksekusi lokal yang digunakan selama proses standarisasi metrik:
| Komponen | Spesifikasi Perangkat |
|---|---|
| Sistem Operasi | Windows 11 Home Single Language (Build 26100) |
| Prosesor | AMD Ryzen 3 7320U (4 Cores, 8 Threads, Base 2.4GHz) |
| Memori Utama | 8GB LPDDR5 Dual-Channel @ 5500 MT/s |
| Arsitektur Compiler | Go 1.25.0 amd64 (CLI Environment) |
-
Efek Startup Awal: Saat memanggil
gonik.InitDatabase(), sistem akan memakan waktu beberapa milidetik untuk membangun peta hash map internal di Heap RAM (Peak Heap Allocawal berkisar$\approx 2.14\text{ MB}$ ). Pemuatan ini disarankan dieksekusi di fungsiinit()atau blok awalmain()sebelum server HTTP mendengarkan request. -
Hindari Variabel Pointer: Untuk mempertahankan performa Zero-Allocation di sistem Anda sendiri, pastikan tidak mengubah variabel instansiasi
gonik.New()menjadi tipe pointer (*Parser) secara manual atau melemparkannya ke fungsi luar yang memicu escape analysis ke heap.
Kontribusi dalam bentuk perbaikan bug, optimasi performa murni, peningkatan cakupan pengujian (test coverage), maupun pembaruan dataset wilayah sangat diapresiasi. Library ini menganut prinsip efisiensi ekstrem, jadi pastikan setiap perubahan kode tetap menjaga status Zero-Allocation.
Silahkan berkontribusi dengan langkah-langkah berikut:
-
Fork repositori ini ke akun GitHub Anda.
-
Clone hasil fork tersebut ke lingkungan lokal Anda:
git clone https://github.com/USERNAME/gonik.git cd gonik -
Buat branch baru untuk fitur atau perbaikan Anda:
git checkout -b fitur-atau-perbaikan-anda
-
Lakukan perubahan kode dan pastikan seluruh unit test serta benchmark lolos tanpa memicu alokasi memori baru:
go test -v ./... go test -bench=. -benchmem
-
Commit perubahan Anda, push ke GitHub, dan buka sebuah Pull Request ke branch utama (
main) repositori ini.
Proyek ini dilisensikan di bawah ketentuan MIT License.
Dataset wilayah dan referensi arsitektur data diadaptasi secara radikal dari basis struktur data mul14/nik_parser.