Upsert Idempoten di Entrypoint, Biar Data Chat Gak Selisih Lagi
Data baseline chat bot lokal dan VPS selisih karena seeding manual. Solusinya: seed JSON di repo di-upsert via entrypoint container.
Ringkasan
Data knowledge di VPS kurang karena baseline diisi lewat copy-paste SQL manual yang rawan kelewat baris. Solusinya: seed file di repo di-upsert otomatis tiap container start pakai INSERT ON DUPLICATE KEY UPDATE dengan slug sebagai kunci alami. Pelajarannya: affected-rows 2 berarti update satu baris, dan dev-prod sebaiknya parity agar bug serupa tidak lolos.
Buka terminal di VPS, cek endpoint knowledge satu-satu lewat REST. Lokal punya 12 entri, di VPS tinggal 8. Dugaan pertama saya: waktu setup VPS kemarin, ada INSERT yang kelewat diketik.
Ternyata bukan sekadar kelewat. Saya emang ngisi data baseline lewat copy-paste SQL manual tiap kali setup environment. Kadang jalan, kadang kelewat satu-dua baris. Dan selisih itu nggak pernah ketutup sendiri, karena nggak ada mekanisme yang nyamain.
Seeder Jalan Tiap Container Start
Solusinya: baseline data dibawa di repo, terus di-upsert otomatis tiap container start. Di repo, commit ini nambah seed_knowledge.py yang dipanggil entrypoint.sh setelah pembuatan schema database selesai. Seeder baca knowledge_seed.json yang isinya 6 entri baseline, lalu jalankan per baris.
Inti SQL-nya satu statement:
INSERT INTO knowledge_entries
(slug, title, category, content, keywords, is_active)
VALUES
(:slug, :title, :category, :content, :keywords, :is_active)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
content = VALUES(content)
Menurut dokumentasi MariaDB, INSERT ... ON DUPLICATE KEY UPDATE itu upsert: kalo ketemu duplikat di unique key atau primary key, INSERT-nya berubah jadi UPDATE [1]. Kuncinya di slug sebagai natural key. Entri yang sama pasti slug-nya sama, entri baru pasti slug-nya beda. PK auto-increment nggak bisa dipakai buat ini karena nilainya beda antar environment, lokal bisa ID 15 padahal VPS ID 8 untuk data yang sama persis.
Aturan mainnya dua. Repo menang buat slug yang ada di seed file, tiap restart nilai seed nge-timpa isi tabel. Slug lain yang datang dari REST tetap aman, upsert by slug nggak pernah nge-hapus baris. Dan kalo seeding gagal, entrypoint cuma print catatan, container tetap hidup, seed dicoba lagi di restart berikutnya.
Angka 2 yang Bikin Saya Salah Paham
Waktu ngetes, saya kira angka affected-rows 2 berarti dua baris keubah. Ternyata itu kontrak statement ini: 1 kalo baris baru ke-insert, 2 kalo baris lama ke-update [2]. Satu upsert cuma menyentuh satu baris, tapi laporannya dua. Saya hampir nulis log yang salah, untung cek dokumentasi dulu.
Detail lain yang bikin saya berhenti sebentar: 0 muncul kalo baris existing diset ke nilai yang sama persis, kecuali koneksi pakai flag CLIENT_FOUND_ROWS, baru jadi 1 [2].
Satu jebakan lagi yang catat di kepala: jangan pakai statement ini di tabel yang punya lebih dari satu unique index. Kalo dua unique index kena sekaligus, cuma yang pertama ke-update, sisanya diabaikan [1].
VALUES() Masih Aman di MariaDB, Deprecated di MySQL
Kode di atas sengaja saya tulis pakai VALUES(title). Di MariaDB yang saya pakai, pola ini masih valid dan dokumentasinya masih jadi rujukan resmi [1]. Tapi di MySQL, penggunaan VALUES() untuk referensi baris baru deprecated mulai 8.0.20 dan penggantinya row alias AS new [2].
Ini alasan saya nggak buru-buru ganti, tapi nggak lupa. Kalo suatu hari tabel ini migrasi ke MySQL native, semua VALUES(...) di clause UPDATE harus dirombak ke alias.
Pelajaran yang lebih besar datang dari Twelve-Factor: backing service di development dan production sebaiknya sama type dan version-nya, karena inkompatibilitas kecil antara keduanya suka muncul sebagai bug yang lolos di dev [3]. Seeder di entrypoint itu aplikasi praktisnya. Selama seed file yang sama dijalankan di kedua sisi, data baseline konvergen tanpa saya sentuh. Persis masalah yang dulu bikin saya copy-paste SQL manual.
Sekarang prosedur setup VPS tinggal pull repo dan start container. Data knowledge yang hilang kemarin balik sendiri begitu container naik, tanpa satu baris SQL pun saya ketik.
Serupa tapi nggak sama, kasus jam yang zonk karena MariaDB ngasih datetime naive pernah saya tulis di datetime naive yang bikin waktu API zonk. Dua-duanya kelihatan sepele di lokal, tapi baru keliatan pas VPS jalan.
Sumber:
- Dokumentasi MariaDB, INSERT ON DUPLICATE KEY UPDATE [1]
- MySQL 8.4 Reference Manual, INSERT ON DUPLICATE KEY UPDATE Statement (dibaca via snapshot Wayback 2026-08-06) [2]
- The Twelve-Factor App, Dev/prod parity [3]