Catatan Saya tentang Desain Skema Postgres GitLab
- Dengan mempelajari skema Postgres GitLab, saya ingin membandingkannya dengan skema yang saya rancang sendiri dan mempelajari praktik terbaik dari definisi skema GitLab.
- GitLab adalah platform DevOps open source, alternatif GitHub, dan dapat di-host sendiri.
Penggunaan Tipe Kunci Utama yang Tepat
- Saat database masih kecil, hal ini tidak terlalu terlihat, tetapi seiring pertumbuhan, kunci utama memengaruhi penggunaan ruang penyimpanan, kecepatan tulis, dan kecepatan baca.
- Dari 573 tabel, GitLab menggunakan tipe kunci utama
bigserialpada 380 tabel,serial4pada 170 tabel, dan 23 sisanya menggunakan kunci utama komposit.
Penggunaan ID Internal dan Eksternal
- Praktik yang baik adalah tidak mengekspos kunci utama ke dunia luar.
- GitLab menggunakan baik
id(ID internal) maupuniid(ID eksternal) pada tabel sepertiissues,ci_pipelines,deployments, danepics.
Penggunaan tipe data text dan Constraint CHECK
- Skema GitLab menggunakan
character varying(n)dantext, tetapi lebih sering menggunakan tipetext. - Tipe
texttidak memiliki batasan panjang, dan batasan panjangnya didefinisikan dengan menggunakanCHECK.
Konvensi Penamaan
- Semua tabel menggunakan bentuk jamak dan memberi ruang nama dengan menggunakan awalan nama modul.
- Nama tabel dan kolom mengikuti aturan snake_case.
Penggunaan Zona Waktu pada Timestamp
- GitLab menggunakan
timestamp with timezonedantimestamp without timezone. - Untuk pekerjaan sistem, GitLab menggunakan
timestamp without timezone, dan untuk pekerjaan pengguna menggunakantimestamp with timezone.
Constraint Kunci Asing
- GitLab menggunakan constraint kunci asing pada sebagian besar tabel, tetapi pada beberapa tabel seperti
audit_events,abuse_reports,web_hooks_logs, danspam_logs, GitLab tidak menggunakannya.
Partisi pada Tabel Besar
- GitLab melakukan partisi pada tabel-tabel yang dapat membengkak agar performa query meningkat.
Dukungan Kasus Penggunaan Pencarian LIKE dengan Trigram dan gin_trgm_ops
- GitLab menggunakan indeks GIN (Generalized Inverted Index) untuk melakukan pencarian yang efisien.
Penggunaan jsonb
- Skema GitLab menggunakan tipe data
jsonbdi beberapa tabel.
Tip Lainnya
- Tabel yang dapat diperbarui menggunakan field audit seperti
updated_at, sedangkan pada tabel log yang tidak dapat diperbarui field tersebut tidak digunakan. - Enum disimpan sebagai
smallintalih-alihcharacter varyinguntuk menghemat ruang.
Pendapat GN⁺:
- Desain skema GitLab memberikan wawasan mendalam tentang desain basis data dan memuat pelajaran penting tentang optimasi skema untuk sistem berskala besar.
- Karena GitLab bersifat open source, keputusan desain skema semacam ini memberi contoh praktis yang dapat diadopsi pengembang lain untuk proyek mereka.
- Hal yang bisa dipelajari dari skema GitLab adalah bahwa pemilihan tipe data, strategi pengindeksan, partisi, dan penggunaan constraint kunci asing sangat memengaruhi kinerja dan pemeliharaan basis data sehingga perlu dipertimbangkan dengan cermat.
1 komentar
Komentar Hacker News
Saya mempertanyakan mengapa praktik untuk tidak mengekspos primary key ke luar itu diperlukan. Jika request pada akhirnya memang harus diautentikasi, saya juga tidak tahu apa nilainya mencegah ID ditebak
Jika hanya dengan ID yang ditebak seseorang bisa melakukan sesuatu yang berguna tanpa autentikasi dan otorisasi, berarti sudah ada bagian lain yang rusak parah, dan seharusnya fokus ke sana daripada menambahkan kompleksitas yang tidak perlu pada skema. Menyembunyikan competitive intelligence agar pesaing tidak bisa memperkirakan jumlah pelanggan mungkin ada nilainya, tetapi rasanya GitLab tidak terlalu memedulikan hal itu. Keputusan GitLab soal
id + iidtampaknya lebih mungkin didorong oleh kebutuhan performa query daripada pencegahan tebakan ID internalJika UUID diekspos ke pengguna, bahkan dengan kerentanan yang sama, penyerang harus menebak UUID sehingga jauh lebih sulit, dan mungkin membutuhkan sumber sekunder. Kalaupun terjadi kebocoran data, masih ada waktu untuk merespons dan volume kebocorannya bisa dihitung. Sebaliknya, jika ID berurutan, masalahnya bisa langsung membesar menjadi kebocoran penuh, dan menjadi insiden besar yang wajib dilaporkan ke otoritas pengawas privasi. Ini adalah defense in depth yang seharusnya tidak perlu berguna, tetapi dalam praktiknya ada perangkat lunak buruk yang benar-benar berhasil ditembus dengan cara seperti ini
Jika primary key
idpada tabel issue diekspos, saat membuat issue di sebuah proyek nilainya tidak dimulai dari 1, sehingga mudah menebak berapa banyak issue yang ada di seluruh GitLabTentu saja, jika sesuatu bisa dilakukan hanya dengan ID yang ditebak tanpa autentikasi dan otorisasi, itu memang masalah besar. Namun jika pada titik itu tidak ada lapisan lain, permainannya sudah selesai. Bug tidak memberi tahu lebih dulu sebelum muncul, terutama bug yang halus. Ketika bug seperti itu muncul, jika setidaknya ID sudah dibuat tidak dapat ditebak, Anda akan bersyukur karena terhindar dari situasi ketika semua akun pengguna di sistem bisa diakses dengan mudah
Tentu saja yang harus diprioritaskan lebih dulu adalah memastikan kontrol akses yang benar, tetapi ID yang tidak dapat ditebak bisa menjadi pembeda antara bencana mengerikan dan nyaris celaka. Jika UUID tidak cocok, bisa juga memakai ID database auto-increment lalu mengenkripsinya; dengan lapisan perangkat lunak yang tepat, ID terenkripsi pun hampir bisa bekerja secara otomatis
Dari 128 juta repositori publik, sebagian besar adalah fork dari repositori lain dan hanya ada untuk membuat pull request ke repositori utama, jadi kecuali ada kesalahan, kemungkinan tidak punya issue
Proyek mainan kecil atau proyek yang cepat ditinggalkan juga kemungkinan tidak punya issue atau hanya sangat sedikit. Pasti ada proyek dengan ratusan atau ribuan issue, tetapi rata-rata keseluruhan dari 128 juta repositori kemungkinan cukup rendah sehingga tetap berada di bawah batas 2 miliar. Meski begitu, saya setuju bahwa penggunaan tipe 4 byte pada tabel tersebut—tepatnya 31 bit—adalah bom waktu bagi sebagian organisasi, termasuk github.com
https://play.clickhouse.com/play?user=play#U0VMRUNUIHVuaXEoc...
Kebutuhan mendasar seperti
PRIMARY KEY(repo_id, issue_id)menjadi tidak perlu rumit di dalam ActiveRecord, dan perlu jalan memutar dengan memakai unique key bersama primary key terpisah agar sesuai dengan ActiveRecord yang menuntut satu kolom primary key. Primary key UUID juga terlihat seperti jalan memutar, tetapi constraint unik untuk pasangan(repo_id, issue_id)tetap diperlukan, sehingga ukuran database dan overhead meningkat. Secara lebih luas, menurut saya arsitektur MVC monolitik Ruby on Rails yang berbasis pada lapisan model, controller, dan view tunggal juga menimbulkan masalah skalabilitas dan maintainability seiring aplikasi membesar; MVC lebih cocok dalam struktur modular atau berbasis komponenProduk yang menyediakan self-hosted sekaligus cloud sering kali lebih menyukai database per pelanggan. Sebab bagian codebase yang dibagikan menjadi jauh lebih sederhana karena dapat menggunakan query yang sama terlepas dari jenis hosting. Jika database-nya per pelanggan, hampir tidak mungkin mendekati batas penggunaan seperti itu, dan jika sampai mendekatinya, self-hosting mungkin lebih cocok
intkebigintitu memungkinkan. Perlu persiapan dan sedikit kode kustom, tetapi bisa dilakukan tanpa downtimeSecara umum kami mengelola migrasi besar dengan mengikuti prosedur ini, dengan sedikit modifikasi: http://zemanta.github.io/2021/08/25/column-migration-from-in...
Foreign key, index, dan constraint secara keseluruhan membuat prosesnya sulit, tetapi bukan tidak mungkin. Dalam kasus saya, migrasi data memakan waktu beberapa jam, tetapi memang tidak perlu cepat. Setahu saya GitLab memiliki alat untuk menjalankan pekerjaan pasca-upgrade agar bisa berfungsi di titik mana pun selama upgrade versi
Pembahasan soal ukuran penyimpanan kolom UUID kurang meyakinkan. Jika tabel punya 5 kolom lain, selisih 128-bit vs 64-bit tidaklah besar
Kekhawatiran yang lebih penting adalah performa. UUIDv4 didukung luas, tetapi karena sepenuhnya acak, ia tidak ideal untuk performa indeks. UUIDv7[0] lebih dekat ke Snowflake[1] dan memiliki lokalitas temporal, tetapi implementasinya belum tersebar seluas itu. Pendekatan terpisah lainnya adalah memakai
bigseriallalu mengenkripsi kuncinya: https://github.com/abevoelker/gfc64Namun cara ini 1) tidak memungkinkan rotasi nilai rahasia, dan 2) begitu bocor, siapa pun bisa membuat estimasi Fermi atas ukuran tabel. Memisahkan ID publik dan ID internal itu merepotkan, dan jika ID publiknya UUIDv4, performa juga dikorbankan. Menurut saya UUIDv7 adalah solusi yang memenuhi paling banyak syarat
[0]: https://uuid7.com/
[1]: https://en.wikipedia.org/wiki/Snowflake_ID
idtersebut dipakai sebagai foreign key, serta ukuran indeks yang diperlukan untuk kolom foreign key tersebutBayangkan nilai seperti ID pengguna yang bisa direferensikan oleh puluhan atau ratusan foreign key di seluruh database
Ada tiga tingkat dalam performa database. 1) Indeks dan data sama-sama muat di memori. 2) Indeks muat di memori, tetapi data tidak. 3) Indeks dan data sama-sama tidak muat di memori. Kondisi nomor 1 bagus, tetapi jika tidak, Anda harus mati-matian mempertahankan nomor 2. Ukuran indeks dua kali lipat membuat itu lebih sulit
typedef void*. Ukurannya memengaruhi performa secara menyeluruh: penggunaan memori dan disk, bottleneck throughput, hingga waktu CPU untuk membandingkan key di loop terdalam pada join dan lookupKetika CPU x86-64 baru keluar, dampak performa dari peralihan ke pointer 64-bit begitu besar sehingga x32/ilp32 dibuat, dan itu juga alasan .NET sampai sekarang masih menjadikan “prefer 32-bit” sebagai default. Memakai UUID 128-bit sebagai primary key database adalah kesalahan yang mengerikan
Misalnya, Anda mungkin ingin mengunggah video sebulan sebelum dipublikasikan, tetapi tidak ingin penonton mengetahui fakta itu
Nilai juga bisa dienkripsi saat ditampilkan di URL, email, dan sebagainya: https://wiki.postgresql.org/wiki/Pseudo_encrypt
Dengan begitu, banyak keunggulan indeks berurutan tetap dipertahankan sekaligus key masih bisa diganti. Namun jika key diganti, bookmark akan rusak, tautan yang pernah dikirim lewat email menjadi tidak valid, dan efeknya pada dasarnya sama seperti mengganti semua nama
Ini mungkin sekadar nitpick kecil, tetapi saya ingin menyoroti bagian
textvsvarcharPenulis menghabiskan banyak kata untuk mencoba membuktikan perbedaan performa yang sebenarnya tidak ada, lalu menyimpulkan bahwa “tidak ada perbedaan performa besar” di antara kedua tipe. Ini topik yang sudah lama tuntas, dan bukan “tidak besar”, melainkan “tidak ada”. Wiki PostgreSQL[1] secara eksplisit menyarankan memakai
textkecuali ada alasan yang sangat bagus, dan dokumentasinya[2] juga mengatakan bahwa “untuk banyak tujuan,character varyingbertindak seperti domain di atastext”, serta di kotak Tip berwarna hijau menyatakan bahwa “tidak ada perbedaan performa di antara ketiga tipe ini”. Jadi, fakta bahwa GitLab kebanyakan memakaitexttampaknya berarti mereka membaca dokumentasi dan merancang skema sesuai PostgreSQL, bukan membuat skema “portabel” yang setengah matang[1] https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use...
[2] https://www.postgresql.org/docs/current/datatype-character.h...
Mengubah
varchar(300)menjadivarchar(200)mengharuskan semua baris ditulis ulang, tetapi memperbarui constraint pada kolomtextpada dasarnya hampir gratis dan hanya memerlukan pemindaian seluruh tabel untuk memastikan nilai lama memenuhi constraint baru. Tulisan itu juga mengatakan bahwa menggunakan tipetextdan constraintCHECKmembuat evolusi skema lebih mudah dibandingcharacter varyingatauvarchar(n)saat ada pemeriksaan panjangKlaim bahwa foreign key itu mahal sering diulang, tetapi benchmark-nya jarang ada
Ada banyak cara untuk mengimplementasikannya dengan keliru, tetapi bagaimanapun juga integritas tetap dipaksakan di suatu tempat dalam stack. Untuk memanfaatkan database alih-alih mengimplementasikannya ulang, dibutuhkan pengetahuan dan eksperimen, dan biasanya itu mencegah insiden besar
Penasaran apakah ada yang pernah merangkum atau memperhatikan perbedaan performa antara GitLab dan GitHub
Keduanya aplikasi berbasis Rails, tetapi secara umum waktu muat halaman GitLab terasa buruk dibandingkan GitHub
Chrome dan GitHub akan memakai segala macam trik meskipun merugikan pengguna. Misalnya, saya pernah membuka diff hasil merge di GitHub perusahaan, mencari dengan
Ctrl F, lalu saat tidak ada hasil saya lanjut menelusuri riwayat Git secara manual, sampai baru pada diff ke-100 saya sadar bahwa file terpenting tersembunyi jauh di dalam. Mungkin karena lebih mudah bagi seseorang untuk memenuhi metrik pemuatan halaman dan mendapat promosiAda tulisan dari mantan developer di sini yang mungkin membantu memahami budaya GitLab dan pengabaiannya terhadap performa: https://news.ycombinator.com/item?id=39303323
Saya sendiri tidak memakai GitLab cukup banyak sampai merasakan masalah performanya, tetapi menurut saya tulisan ini bisa membantu
Saya selalu penasaran apa arti tambahan huruf I pada variabel CI
CI_PIPELINE_IIDdanCI_MERGE_REQUEST_IIDSaya menduga itu pilihan terkait database, dan tulisan ini mengonfirmasinya
Melihat bahwa “1 quintillion sama dengan 1.000.000.000 billion”, rasanya cukup aneh bahwa biasanya kita hanya memilih antara
int32danint64. Sepertinya seharusnya ada tipe integer 5 byte yang mendukung kardinalitas sekitar 1 triliunTidak memakai ID auto-increment bisa masuk akal, tetapi saya kurang paham manfaat punya 2 ID, satu untuk internal dan satu untuk eksternal
Jumlah kolom dan indeks bertambah, selalu harus melakukan lookup lebih dulu, dan saya tidak terpikir skenario keamanan di mana kunci internal diubah sementara kunci eksternal tidak. Apa ada yang saya lewatkan?
Katanya jika memakai tipe UUID v4 native PostgreSQL alih-alih
bigserial, ukuran tabel bertambah 25% dan laju insert turun menjadi 25% daribigserial; saya penasaran kenapa UUIDv4 bisa seburuk ituBukankah UUID hanya angka 128-bit? Apakah biaya pembuatannya sangat mahal, atau apa yang sebenarnya terjadi?
Karena itu pekerjaan pengindeksan kolom UUIDv4 menjadi lambat, dan inilah motivasi pengembangan UUIDv6 dan UUIDv7
Masalah yang lebih besar adalah laju insert. Untuk UUID, laju insert dibatasi oleh jumlah RAM yang tersedia. Integer auto-increment tidak begitu. Integer berkorelasi dengan waktu, tetapi UUID4 acak, sehingga pada skala besar karakteristik performanya berubah secara fundamental. Pada tabel kecil, penalti insert hampir bisa diabaikan, tetapi ketika ukuran indeks B-tree menyentuh batas memori, PostgreSQL tidak bisa menyimpan seluruh B-tree UUID di memori dan harus bergantung pada pergantian halaman disk. Integer auto-increment membuat baris yang berdekatan secara waktu memakai halaman indeks yang sama, sehingga pada beban yang sama tidak perlu menyentuh disk. Begitu mencapai skala ini, perbedaannya bukan penurunan tetap 25%, melainkan jurang performa 25 kali lipat, dan selain migrasi skema, satu-satunya pilihan adalah membeli lebih banyak RAM
UUID menghasilkan banyak write yang tidak terurut, yang berujung pada page bloat
Lokalitas batch insert juga memburuk saat di-query nanti, sehingga record terkait harus dicari secara acak. Pada akhirnya biaya dibayar baik saat insert maupun saat select berikutnya