- Menganalisis batas performa Bundler sambil membandingkan alasan package manager Python uv bisa sangat cepat
- Kecepatan uv bukan karena bahasa Rust, melainkan berkat desain struktural seperti unduhan paralel, cache global, dan pemrosesan dependensi berbasis metadata
- Bundler memiliki keterbatasan paralelisasi karena proses unduh dan instalasi saling terikat, dan memisahkannya berpotensi memberi peningkatan besar
- Integrasi cache global, instalasi dengan hardlink, dan integrasi solver PubGrub dapat mengurangi duplikasi antara RubyGems dan Bundler
- Bahkan tanpa menulis ulang ke bahasa lain, sebagian besar peningkatan performa bisa dicapai di dalam kode Ruby, dan berpotensi mendekati kecepatan uv
Perbandingan performa Bundler dan uv
- Penyelidikan terhadap bottleneck performa Bundler dimulai dari pertanyaan yang muncul di RailsWorld: “mengapa Bundler tidak secepat uv?”
- Penulis yakin Bundler bisa mencapai kecepatan setara uv, dan menegaskan bahwa perbedaan performa adalah masalah desain, bukan bahasa
- Dengan mengutip tulisan Andrew Nesbitt “How uv got so fast”, artikel ini menganalisis apakah teknik optimasi utama uv dapat diterapkan ke Bundler
Apakah perlu ditulis ulang ke Rust?
- Memang benar uv ditulis dalam Rust, tetapi penyebab mendasar kecepatannya bukan Rust itu sendiri
- Jika bottleneck Bundler berhasil dihilangkan hingga “menulis ulang ke Rust menjadi satu-satunya peningkatan yang tersisa”, itu justru dianggap sebagai keberhasilan
- Penulisan ulang ke Rust memang memberi kebebasan untuk mencoba desain eksperimental tanpa batasan kompatibilitas lama, tetapi itu bukan syarat mutlak
Bottleneck struktural Bundler
- Bundler menggabungkan unduhan gem dan instalasi dalam satu method, sehingga unduhan paralel tidak memungkinkan
- Pada contoh kode, method
install menjalankan fetch_gem_if_not_cached lalu install secara berurutan
- Akibatnya, gem yang memiliki relasi dependensi (
a -> b -> c) hanya bisa dipasang secara berurutan
- Hasil eksperimen menunjukkan bahwa ketika ada dependensi, proses memakan waktu lebih dari 9 detik, sedangkan gem independen (
d, e, f) dapat selesai dalam kurang dari 4 detik lewat unduhan paralel
- Dengan memisahkan unduhan dan instalasi, paralelisasi tetap dimungkinkan tanpa melanggar aturan dependensi
- Diusulkan pemisahan menjadi empat tahap (unduh → ekstrak → kompilasi → instalasi)
- Untuk gem Ruby murni, urutan instalasi dependensi bisa dilonggarkan agar ada peningkatan kecepatan tambahan
Optimasi cache dan instalasi
- Pendekatan cache global dan instalasi hardlink milik uv juga bisa diterapkan ke Bundler
- Saat ini Bundler dan RubyGems memakai cache terpisah untuk tiap versi Ruby
- Perlu disatukan ke cache bersama berbasis
$XDG_CACHE_HOME
- Instalasi hardlink bisa diterapkan setelah integrasi cache selesai
- Bundler sudah memakai solver dependensi PubGrub, tetapi RubyGems masih menggunakan molinillo
- Penyatuan solver di kedua sistem menjadi kunci untuk mengurangi utang teknis
Penerapan elemen optimasi yang terkait Rust
- Deserialisasi zero-copy punya kemungkinan diterapkan sebagian pada tahap parsing YAML di RubyGems
- GVL (Global VM Lock) Ruby tidak terlalu membatasi paralelisasi untuk pekerjaan yang berfokus pada IO
- IO dan pemrosesan ZLIB melepaskan GVL, sehingga dapat berjalan paralel
- Namun, pada penulisan file kecil, overhead pengelolaan GVL bisa menjadi faktor penurun performa
- Sudah ada pekerjaan yang sedang berlangsung di internal Ruby untuk memperbaikinya
- Optimasi perbandingan versi: uv mempercepat perbandingan dengan mengenkode versi sebagai bilangan bulat
u64
- Di Ruby,
Gem::Version juga bisa diubah ke basis bilangan bulat untuk meningkatkan performa solver
- Upaya refaktor terkait sebenarnya sudah pernah ada, tetapi ditunda karena masalah kompatibilitas mundur
Kesimpulan dan rencana ke depan
- Kecepatan uv berasal dari desain yang menghilangkan pekerjaan yang tidak perlu, bukan dari bahasanya, dan Bundler pun bisa ditingkatkan ke arah yang sama
- RubyGems dan Bundler sudah memiliki struktur package management modern, sehingga mencapai kecepatan setara uv adalah target yang realistis
- Tantangan terbesar adalah mempertahankan kompatibilitas dengan kode lama
- Bahkan tanpa penulisan ulang ke Rust, 99% peningkatan performa bisa dicapai di dalam kode Ruby, dan sisa 1%-nya relatif kecil
- Tulisan lanjutan akan membahas profiling nyata Bundler dan RubyGems serta penyebab bottleneck yang lebih spesifik
2 komentar
Omong kosong itu murah. Tunjukkan kodenya!
Komentar Hacker News
Saya tidak terlalu paham struktur internal Bundler, tetapi saya rasa perbaikan terbesar adalah mengadopsi desain cache uv
Salah satu alasan utama uv bisa cepat ada pada struktur cache-nya, dan ini bisa direplikasi di bahasa atau ekosistem lain
Namun, bagian yang mengabaikan batas atas
requires-pythonbukan karena performa, melainkan demi resolusi dependensi yang lebih baikMisalnya, sebuah proyek membutuhkan Python 3.8 atau lebih baru, tetapi ada dependensi yang memberi batasan
<4, maka instalasi akan gagal di Python 4uv melakukan resolusi untuk semua versi yang didukung, jadi mengabaikan batas atas itu hampir tidak menghemat waktu
Diskusi terkait bisa dilihat di forum Python Discuss
Seperti Python Simple Repository API yang sejak PEP 658 menyediakan metadata secara langsung, RubyGems.org juga sudah menyediakan informasi serupa
Namun, kita baru bisa tahu apakah ada native extension setelah gem dibongkar
Jadi ada usulan: kalau informasi ini ditambahkan langsung ke metadata RubyGems.org, bukankah pohon instalasi dependensi bisa diparalelkan sepenuhnya?
Saat dulu bekerja di RubyGems.org, saya ingat metadata diekstrak per versi
Gemspec versi lama harus diproses ulang, dan ini bisa menjadi perubahan metadata yang berisiko
Jadi mungkin sulit diterapkan ke versi lama, tetapi ke depannya sepertinya bisa diperbaiki agar urutan instalasi diketahui tanpa perlu unpack
Saya suka Aaron fokus pada perbaikan algoritma yang benar-benar substantif alih-alih menulis ulang Bundler dalam Rust
Lingkungan yang kacau dengan campuran berbagai alat manajemen versi dan versi Ruby benar-benar menjengkelkan
Menurut saya, masalahnya bukan sekadar kecepatan, melainkan kendali dan arah ekosistem
Ruby selama 10 tahun terakhir berfokus pada kecepatan, padahal kualitas dokumentasi dan pengelolaan komunitas justru lebih penting
Sudah saatnya benar-benar memikirkan mengapa bahasa ini menurun, dan mendorong berbagai ide yang berbeda
Ada tulisan terkait terbaru: How uv got so fast (Desember 2025, 457 komentar)
Jika ingin membuat RubyGems lebih cepat, kuncinya adalah mendaftarkan/menyimpan daftar file tiap gem di registri atau database
Dengan begitu, saat
requiredijalankan, tidak perlu memindai file system setiap kaliJika gem dimodifikasi langsung, metadata memang harus di-hash ulang, tetapi modifikasi manual memang tidak dianjurkan
Mungkin sekarang sudah kuno, tetapi ini tetap proyek mini yang saya sukai
Kode: fastup
Masalah sebenarnya adalah struktur
$LOAD_PATHyang menambahkan semua gem dan menyebabkan ledakan kombinatorialFakta bahwa ada banyak proyek cache adalah bukti bahwa ini memang masalah nyata
Dulu startup aplikasi bisa makan waktu beberapa menit, dan saya pernah memangkasnya dalam hitungan menit dengan memanipulasi load path
Saya dulu pernah mengusulkan agar bootsnap diintegrasikan ke bundler, tetapi ditolak
Penjelasan tentang struktur RubyGems menarik
Sebuah gem adalah file tar, dan YAML GemSpec di dalamnya mendeklarasikan dependensi
RubyGems.org menyediakan informasi ini lewat API, jadi dependensi bisa diperiksa tanpa eval
Namun YAML adalah format yang kurang efisien untuk parsing, jadi alternatif seperti JSON atau protobuf mungkin lebih baik
Meski begitu, kalau gemserver memang sudah mengembalikan informasi dependensi, mungkin ini bukan masalah besar
Contoh: struktur yang hanya memuat versi, dependensi, dan hash
Itulah salah satu alasan uv cepat — perhitungan dependensi bisa dilakukan tanpa mengunduh paket
Dulu saya pernah membuat video prototipe tentang cara instalasi gem seharusnya bekerja
how_gems_should_be.mov
Fibers Ruby (atau library Async) sering kali dilebih-lebihkan
Sama seperti thread, masalah koordinasi level tinggi seperti connection pool tetap ada
Meski begitu, jika pekerjaan instalasi yang bersifat IO-bound ditangani secara asinkron, peningkatan performa yang berarti tetap bisa didapat
Saya mungkin akan mendekatinya seperti itu
Ide “cache global dibagikan oleh semua instance bundler” sedang dikaji
Dalam jangka panjang sepertinya akan sangat menguntungkan, tetapi masih dinilai apakah ada kompleksitas tersembunyi
Isu terkait: rubygems #7249
Ruby bukan yang pertama menyelesaikan masalah ini, jadi sekarang saatnya menikmati manfaatnya
Prinsip dasar optimisasi itu sederhana — tidak melakukan apa pun adalah yang paling cepat
Tidak melakukan pekerjaan yang tidak perlu itulah optimisasi yang sesungguhnya