← semua materi
19:00 Tips · 05 Nov 2026 · 19:01

Kenapa emoji bisa bikin database error — dan kenapa "panjang string" itu pertanyaan yang salah

Coba tebak: berapa "panjang" string "😀"? Jawabannya tergantung siapa yang ditanya — dan di situlah bug-nya bersembunyi.


Satu karakter, empat jawaban berbeda

"😀" itu 1 karakter kalau kamu tanya ke manusia, tapi:

Yang dihitung Hasil
Codepoint (Unicode) 1
Byte UTF-8 4
Byte UTF-16 4 (2 unit × 2 byte)
Kolom VARCHAR(1) di MySQL utf8 lama error / terpotong

Saya cek langsung barusan:

codepoints 1
utf8 bytes 4  f0 9f 98 80
utf16 units 2  3d d8 00 de

Empat byte. Sementara huruf A cuma 1 byte di UTF-8.

Akar masalahnya: UTF-8 itu panjang variabel

UTF-8 pakai 1–4 byte per codepoint:

UTF-16 pakai 2 atau 4 byte. Karakter di atas U+FFFF (kebanyakan emoji) butuh surrogate pair — dua unit 16-bit. Makanya "😀" jadi 2 unit UTF-16. Di JavaScript dan Java, "😀".length mengembalikan 2, bukan 1. Itu bukan bug JS — itu konsekuensi historis UTF-16.

Di Python 3 len("😀") = 1 karena string dihitung per codepoint. Jadi kode yang sama, bahasa berbeda, angka berbeda. Kalau kamu port logika validasi panjang dari JS ke Go tanpa mikir, kamu akan dapat off-by-one yang cuma muncul kalau user ketik emoji. Bug yang lolos QA 11 bulan.

Yang lebih parah: emoji keluarga

family codepoints 7  utf8 bytes 25  utf16 units 11

👨👩👧👦 — yang terlihat seperti satu emoji — sebenarnya 7 codepoint: 4 emoji orang + 3 zero-width joiner (U+200D). Di UTF-8 jadi 25 byte.

Artinya:

Tiga jebakan operasional yang paling sering kena

1. MySQL utf8 bukan UTF-8. utf8 di MySQL = UTF-8 3-byte maksimum. Emoji 4-byte ditolak atau jadi ???. Yang benar: utf8mb4. Ini jebakan paling klasik dan masih hidup di 2026 karena nama kolomnya menipu. Cek:

SHOW VARIABLES LIKE 'character_set_%';

Kalau ada utf8 (bukan utf8mb4), itu bom waktu.

2. Potong string berdasarkan byte, bukan codepoint. Preview 100 karakter dengan substr($text, 0, 100) di PHP (byte-based) akan memotong di tengah karakter multibyte → � di akhir preview. Solusi: pakai fungsi yang sadar encoding (mb_substr), atau lebih baik potong per grapheme cluster.

3. Normalisasi Unicode. "é" bisa ditulis dua cara: satu codepoint (U+00E9) atau e + combining accent (U+0065 U+0301). Keduanya terlihat identik, tapi "é" != "e\u0301" di perbandingan string. Ini bikin:

Solusi: unicodedata.normalize('NFC', s) sebelum simpan/bandingkan. Saya verifikasi: setelah NFC, panjangnya jadi 1.

Urutan konsep yang bener (dari kecil ke besar)

byte  <  codepoint  <  grapheme cluster

Aturan praktis: validasi dan potong pakai grapheme, simpan pakai byte, hitung "jumlah karakter" untuk user pakai grapheme. Kalau tidak mau ribet, minimal pakai codepoint — jangan pernah byte.


Satu hal yang bisa langsung dipakai

Sebelum bikin kolom teks baru, jalankan ini sekali:

ALTER TABLE your_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Dan di aplikasi, jangan pernah pakai strlen-setara untuk validasi panjang teks user. Ganti dengan hitungan grapheme:

Kalau hari ini kamu cuma sempat satu: cari kata utf8 (tanpa mb4) di config database-mu. Itu 5 menit, dan menyelamatkan kamu dari bug yang bakal muncul 6 bulan lagi saat user pertama kali pakai emoji.


Takeaway

"Panjang string" bukan satu angka — dia byte, codepoint, dan grapheme sekaligus. Bug emoji bukan soal emoji; itu soal kode yang mengasumsikan satu karakter = satu byte. ASCII membuat kita malas selama 30 tahun, dan Unicode menagihnya sekarang.