- Saat menggunakan GitHub API, muncul masalah pada fitur pembuatan tautan komentar PR karena ketidakcocokan ID sehingga tautan tidak berfungsi
- Hasil investigasi menunjukkan bahwa GitHub menggunakan dua sistem ID secara bersamaan: node ID di GraphQL dan database ID di REST API
- Setelah base64 decoding pada node ID, ditemukan bahwa 32 bit terbawah berisi database ID, sehingga dapat dikonversi dengan operasi bitmask sederhana
- Analisis lanjutan mengungkap bahwa GitHub mencampur format ID baru berbasis MessagePack dan format lama berbasis string
- Struktur ini menunjukkan dualisme sistem identifikasi objek internal GitHub, dan pengembang perlu berhati-hati saat mengintegrasikan API
Penemuan sistem ID ganda GitHub
- Saat mengembangkan fitur pada alat AI code review milik Greptile, muncul masalah ketika tautan komentar PR GitHub tidak berfungsi
- ID komentar yang disimpan telah disambungkan ke URL, tetapi saat diklik tidak berpindah ke halaman GitHub
- Setelah memeriksa dokumentasi GitHub, ditemukan bahwa node ID pada GraphQL API dan database ID pada REST API merupakan dua sistem yang berbeda
- Contoh node ID:
PRRC_kwDOL4aMSs6Tkzl8
- Contoh database ID:
2475899260
- node ID adalah string hasil encoding base64 untuk mengidentifikasi objek secara global di seluruh GitHub, sedangkan database ID digunakan sebagai pengenal URL berbentuk bilangan bulat
Analisis hubungan antara node ID dan database ID
- Dengan membandingkan node ID dan database ID dari beberapa komentar PR, terlihat bahwa kedua nilai meningkat dengan pola interval yang konsisten
- Ketika bagian base64 dari node ID didekode, dihasilkan integer 96-bit, dan 32 bit terbawah dari nilai ini cocok dengan database ID
- Contoh:
PRRC_kwDOL4aMSs6Tkzl8 → 32 bit terbawah = 2475899260
- database ID dapat diekstrak dengan operasi bitmask sederhana
- Konversi dilakukan dengan operasi berbentuk
decoded & ((1 << 32) - 1)
Format ID lama GitHub
- Ketika node ID dari repositori lama (
torvalds/linux) didekode, muncul string dengan format berbeda
- Contoh:
MDEwOlJlcG9zaXRvcnkyMzI1Mjk4 → 010:Repository2325298
- Format ini memiliki struktur
[nomor tipe objek]:[nama objek][Database ID] dan merupakan identifier eksplisit berbasis string
- Untuk objek tree, bentuknya seperti
04:Tree2325298:7201bfb9..., yang juga memuat ID repositori dan nilai SHA
- GitHub menggunakan format lama dan format baru secara bersamaan, dan formatnya berbeda tergantung jenis objek serta waktu pembuatannya
Struktur format node ID baru
- Panduan migrasi GraphQL GitHub menyatakan bahwa node ID harus diperlakukan sebagai string opak, tetapi tetap ada struktur internal di dalamnya
- Setelah base64 decoding lalu di-unpack dengan MessagePack, muncul data berbentuk array
- Contoh:
[0, 47954445, 2475899260]
- Susunan array tersebut
- Elemen pertama (0): diduga sebagai identifier versi
- Elemen kedua (47954445): database ID repositori
- Elemen ketiga (2475899260): database ID objek
- Panjang array berbeda menurut jenis objek; commit menyertakan SHA, sedangkan repositori hanya memiliki dua elemen
Pemanfaatan praktis dan kesimpulan
1 komentar
Komentar Hacker News
GitHub global node ID terbaru bisa dipaksa digunakan lewat header
'X-Github-Next-Global-ID'ID tersebut terdiri dari prefiks tipe objek dan payload msgpack yang di-encode dengan base64
Misalnya, ID pengguna saya
"U_kgDOAAhEkg"ter-decode menjadi[0, 541842], yang cocok dengandatabaseIddi REST APINamun, sebaiknya jangan bergantung pada implementasi internal seperti ini; lebih baik langsung meminta field
databaseIddari GraphQL APIDokumentasi terkait: panduan migrasi GraphQL global node ID, info pengguna GitHub saya, contoh decoding CyberChef, implementasi GitHub ETag
Mendekode dengan cara seperti ini menurut saya rapuh
Global node ID di GraphQL pada dasarnya harus bersifat opaque
Berbagai tipe GitHub (seperti PullRequest) menyediakan field
databaseId, jadi itulah yang seharusnya dipakaiSebagian besar GraphQL API memang meng-encode nama tipe dan DB ID dengan base64, tetapi tidak ada jaminan pola ini akan selalu dipertahankan
Referensi: dokumentasi objek PullRequest, spesifikasi GraphQL global ID
permalink,url, dan interfaceUniformResourceLocatable, jadi tidak perlu menyusun URL secara manualItulah sebabnya API menyediakan permalink. Pola ID atau tautan bisa berubah kapan saja
Pendekatan seperti ini juga sering dipakai pada token pagination
ID seperti
010:Repository2325298punya struktur yang jelas010adalah enum tipe,Repositoryadalah nama, dan2325298adalah DB IDJadi, ini berbentuk length prefix. Repository panjangnya 10 karakter, Tree panjangnya 4 karakter
Opus 4.5 mengetahui trik decoding ID GitHub seperti ini, dan otomatis menulis kode untuk mendekodenya
Apa yang ditemukan penulis memang benar secara teknis, tetapi tidak terdokumentasi dan tidak didukung
GitHub juga pernah diam-diam mengubah struktur internal node ID di masa lalu
Jika mereka menambahkan field ke array MessagePack, mengubah encoding, mengenkripsi, atau beralih ke UUID,
sistem yang bergantung pada struktur internal seperti ini akan langsung rusak
Identifier GitHub yang saya simpan secara eksplisit paling banter hanyalah kunci URL yang tidak berubah (nomor issue/PR atau hash commit)
ID komentar saya masukkan begitu saja ke dalam blob JSON
Tidak perlu mencoba menormalkan semuanya. JSON sudah cukup cepat
Kecuali Anda melakukan kueri lintas-komentar, hampir tidak akan muncul sebagai masalah performa
Jika repositori berganti nama atau dipindahkan ke organisasi lain, URL-nya bisa berubah
Di API v3 lama, tidak ada ID, jadi jika seseorang mengganti nama pengguna atau nama repositori, sulit melacak siapa mereka
Karena itu saya pernah membuat sendiri sistem manajemen kepemilikan berbasis tim
Terraform provider-nya kurang bagus, sehingga saat offboarding sering muncul masalah seperti “orang yang keluar ternyata satu-satunya admin”
Semua repositori dimiliki oleh tim, dan hak akses juga hanya diberikan per tim
Kontrol akses berbasis tim seperti ini berguna bukan hanya di GitHub, tetapi juga di sistem lain
Ini contoh klasik Hyrum’s Law — ketika orang mulai bergantung pada perilaku yang tidak terdokumentasi, pada akhirnya pasti akan rusak
Dalam desain basis data, biasanya ke pihak luar diberikan natural key yang opaque, sementara di internal digunakan ID integer yang bertambah
Tetapi jika memakai ID komposit, masalah ini berkurang.
Misalnya, jika ID repositori mencakup ID objek, maka menaikkan ID hanya akan menjelajahi objek dalam repositori yang sama
Jika ditambah entropi atau timestamp, penyalahgunaannya nyaris mustahil
Karena itu lebih aman mengekspos surrogate key yang tidak bermakna
Misalnya, YouTube mungkin memakai nomor indeks secara internal, tetapi ke luar memberikan ID berbentuk kode yang tidak bermakna
Sekarang jadi masuk akal kenapa tim GitHub dalam beberapa tahun terakhir sangat memperluas dukungan sharded/multi-database di Rails