- ตัวจัดการแพ็กเกจ หลายตัวเลือก ใช้ Git ราวกับเป็นฐานข้อมูล เพราะสะดวกต่อการจัดการเวอร์ชันและการทำงานร่วมกัน แต่เมื่อระบบใหญ่ขึ้นก็เจอปัญหาด้านประสิทธิภาพและการบำรุงรักษา
- Cargo, Homebrew, CocoaPods เป็นต้น ต่างต้องเปลี่ยนไปใช้ ดัชนีแบบ HTTP หรือ CDN ในที่สุด เนื่องจากขนาดของ Git index ที่โตขึ้น ความช้าในการอัปเดต และความไม่มีประสิทธิภาพในสภาพแวดล้อม CI
- vcpkg ยังทำงานโดยอิงกับ Git tree hash และในสภาพแวดล้อมแบบ shallow clone ก็เกิด การบิลด์ล้มเหลวและวิธีแก้ชั่วคราวที่ซับซ้อน
- ระบบ Go modules นำ GOPROXY และ ฐานข้อมูล checksum (sumdb) มาใช้ เพื่อตัดการพึ่งพา Git และปรับปรุงทั้งความปลอดภัยและความเร็ว
- Git ยอดเยี่ยมสำหรับการทำงานร่วมกันบนโค้ด แต่มีหลักฐานซ้ำๆ ว่า ไม่เหมาะกับการ query เมทาดาทาของแพ็กเกจหรือการจัดการ registry ขนาดใหญ่
ความล้มเหลวที่เกิดซ้ำของความพยายามใช้ Git เป็นฐานข้อมูล
- Git มีข้อดีอย่าง ประวัติเวอร์ชัน, โครงสร้างแบบกระจายศูนย์, และโฮสติ้งฟรี จึงดูน่าสนใจ แต่เมื่อใช้เป็นฐานข้อมูลก็จะชนกับข้อจำกัดด้านการขยายระบบ
- ตัวจัดการแพ็กเกจหลายตัวนำ Git มาใช้เป็นดัชนี แต่เมื่อเวลาผ่านไป ปัญหาประสิทธิภาพและภาระด้านโครงสร้างพื้นฐาน ก็หนักขึ้นเรื่อยๆ
Cargo
- ดัชนีของ crates.io เริ่มต้นในรูปแบบ Git repository และไคลเอนต์ทุกตัวต้องทำการ clone ทั้งหมด
- เมื่อ repository โตขึ้น ก็เกิด คอขวดด้านประสิทธิภาพของ libgit2 ในขั้นตอน delta resolution
- ในสภาพแวดล้อม CI ต้องดาวน์โหลดดัชนีทั้งหมดใหม่ทุกครั้งที่บิลด์ ทำให้สิ้นเปลืองอย่างมาก
- ผ่าน RFC 2789 มีการนำ sparse HTTP protocol มาใช้ เพื่อดึงเฉพาะเมทาดาทาที่จำเป็นผ่าน HTTPS
- ณ เดือนเมษายน 2025 คำขอ 99% ใช้ sparse mode
- Git index ยังมีอยู่ แต่ผู้ใช้ส่วนใหญ่ไม่ได้เข้าถึงแล้ว
Homebrew
- GitHub ขอให้ Homebrew หยุดใช้ shallow clone โดยชี้ว่าการอัปเดตเป็น “การประมวลผลที่มีต้นทุนสูงมาก”
- โฟลเดอร์
.git ของ homebrew-core มีขนาดเกือบ 1GB และการอัปเดตเกิดความหน่วงจาก delta resolution
- ใน Homebrew 4.0.0 เดือนกุมภาพันธ์ 2023 การอัปเดต tap ถูกเปลี่ยนเป็น การดาวน์โหลดแบบ JSON
- เมื่อเลิกใช้ Git fetch ความเร็วในการอัปเดตดีขึ้น และรอบการอัปเดตอัตโนมัติก็เปลี่ยนจากทุก 5 นาทีเป็นทุก 24 ชั่วโมง
CocoaPods
- ตัวจัดการแพ็กเกจสำหรับ iOS/macOS อย่าง CocoaPods มี Specs repository ที่ประกอบด้วย podspec หลายแสนรายการจนใหญ่เกินไป
- การ clone และอัปเดตกินเวลาหลายนาที และเวลาส่วนใหญ่ใน CI หมดไปกับการทำงานของ Git
- GitHub ใช้ CPU rate limit และชี้ว่า shallow clone เป็นสาเหตุหนึ่งของภาระบนเซิร์ฟเวอร์
- ทีมงานใช้มาตรการชั่วคราว เช่น หยุด fetch อัตโนมัติ, เปลี่ยนเป็น full clone, และ sharding repository
- ตั้งแต่ เวอร์ชัน 1.8 เป็นต้นมา ได้เปลี่ยนเป็น การแจกจ่ายผ่าน HTTP บน CDN ช่วยประหยัดพื้นที่ดิสก์ของผู้ใช้ราว 1GB และเพิ่มความเร็วในการติดตั้งอย่างมาก
Nixpkgs
- ฝั่งไคลเอนต์ของ Nix ใช้ channel แบบ tarball อยู่แล้ว จึงหลีกเลี่ยงการ clone Git
- package expression ถูกให้บริการผ่าน HTTP จาก S3 และ CDN
- อย่างไรก็ตาม โครงสร้างพื้นฐานของ GitHub ยังรับภาระจาก repository ขนาด 83GB และ fork 20,000 รายการ
- ในเดือนพฤศจิกายน 2025 GitHub รายงาน ความล้มเหลวของฉันทามติระหว่างสำเนาและข้อผิดพลาดในงานบำรุงรักษา
- แม้ local clone จะมีขนาด 2.5GB แต่ทั้งเครือข่ายของ fork ก็ยังกดดันพื้นที่จัดเก็บของ GitHub
vcpkg
- vcpkg ตัวจัดการแพ็กเกจ C++ ของ Microsoft ใช้ Git tree hash ในการจัดการเวอร์ชัน
- หากต้องการทำซ้ำพอร์ต ณ commit หนึ่งผ่าน
builtin-baseline ก็จำเป็นต้องมีประวัติทั้งหมด
- ใน สภาพแวดล้อมแบบ shallow clone (GitHub Actions, DevContainers) จะเกิดการบิลด์ล้มเหลว
- วิธีแก้คือต้องตั้งค่า
fetch-depth: 0 เพื่อดาวน์โหลดประวัติทั้งหมด
- ด้วยโครงสร้างของ Git tree hash ทำให้ ติดตาม commit ไม่ได้ และแก้ไขข้อจำกัดเชิงโครงสร้างนี้ไม่ได้
- ปัจจุบันยังรองรับเฉพาะ registry แบบ Git repository เท่านั้น และ ยังไม่มีทางเลือกแบบ HTTP หรือ CDN
ระบบ Go modules
- ทีมวิศวกรรมของ Grab ระบุว่า หลังนำ module proxy มาใช้ เวลา
go get ลดจาก 18 นาที → 12 วินาที
- วิธีเดิมต้อง clone repository ทั้งหมดของแต่ละ dependency เพื่ออ่าน
go.mod
- ทีม Go กังวลเรื่อง การพึ่งพาเครื่องมือ VCS และช่องโหว่ด้านความปลอดภัย
- ตั้งแต่ Go 1.13 เป็นต้นมา GOPROXY เป็นค่าปริยาย โดยให้บริการ source ของโมดูลและ
go.mod ผ่าน HTTP
- sumdb (ฐานข้อมูล checksum) ช่วยรับประกันความสมบูรณ์และความคงอยู่ของโมดูล
ปัญหาทั่วไปเมื่อใช้ Git เป็นฐานข้อมูล
- Git-based wiki (Gollum) มีปัญหาการไล่ดูไดเรกทอรีและโหลดหน้าที่ช้าลงเมื่อ repository มีขนาดใหญ่
- GitLab มีแผนเลิกใช้ Gollum
- Git-based CMS (Decap) ติดเพดานคำขอของ GitHub API ที่ 5,000 ครั้ง
- ประสิทธิภาพลดลงเมื่อมีรายการราว 10,000 รายการขึ้นไป และผู้ใช้ใหม่ที่เริ่มจากแคชว่างจะทำให้คำขอพุ่งสูง
- เครื่องมือ GitOps (ArgoCD) มีปัญหาพื้นที่ดิสก์ไม่พอเมื่อ clone repository
- commit เดียวสามารถทำให้แคชทั้งหมดใช้ไม่ได้ และ monorepo ขนาดใหญ่ต้องสเกลแยกต่างหาก
เหตุผลเชิงโครงสร้างที่ Git ไม่เหมาะเป็นฐานข้อมูล
- ข้อจำกัดของไดเรกทอรี: ยิ่งมีไฟล์มากก็ยิ่งช้า
- CocoaPods เคยสร้าง tree object ขนาดมหาศาลจากไดเรกทอรี 16,000 รายการ ก่อนแก้ด้วยการ sharding ตาม hash
- ปัญหาความต่างตัวพิมพ์เล็ก-ใหญ่: Git แยก แต่ macOS และ Windows ไม่แยก
- Azure DevOps เพิ่มฟังก์ชันบล็อกฝั่งเซิร์ฟเวอร์เพื่อป้องกันการชนกัน
- ข้อจำกัดความยาว path: ข้อจำกัด 260 อักขระของ Windows ทำให้
git status เกิดข้อผิดพลาด
- ไม่มีฟังก์ชันแบบฐานข้อมูล:
- ไม่มีทั้ง CHECK/UNIQUE constraint, locking, index, หรือ migration
- ตัวจัดการแพ็กเกจแต่ละตัวต้องสร้างระบบตรวจสอบและทำดัชนีขึ้นมาเอง
สรุป
- Git ยอดเยี่ยมสำหรับ การทำงานร่วมกันบนซอร์สโค้ด แต่ ไม่เหมาะกับการ query เมทาดาทาของแพ็กเกจหรือการจัดการ registry ขนาดใหญ่
- ตัวจัดการแพ็กเกจส่วนใหญ่สุดท้ายต้องเปลี่ยนไปใช้ ดัชนีแบบ HTTP หรือฐานข้อมูล
- แม้ข้อดีของ Git อย่างประวัติเวอร์ชันและ workflow แบบ PR จะน่าสนใจ แต่ ในฐานะตัวแทนฐานข้อมูลนั้นล้มเหลว
- ต่อให้ Git index ดูน่าสนใจเมื่อออกแบบตัวจัดการแพ็กเกจใหม่ ก็จะไปถึงข้อจำกัดเดิมเช่นเดียวกับกรณีของ Cargo, Homebrew, CocoaPods, vcpkg และ Go
2 ความคิดเห็น
แทนที่จะสร้างระบบแยกต่างหากเพื่อรับการมีส่วนร่วมจากผู้ร่วมพัฒนา ก็แค่ใช้ git เพราะมันสะดวกกว่าเท่านั้นเอง เรื่องที่บอกว่าเป็นข้อจำกัดนั้นผมไม่ค่อยเห็นด้วยเท่าไร และก็ไม่เห็นทางเลือกสำหรับปัญหาในโลกความเป็นจริงเลยด้วย
ความคิดเห็นบน Hacker News
เรื่องนี้ดูคล้าย โศกนาฏกรรมของทรัพยากรส่วนรวม อยู่เหมือนกัน GitHub ฟรีและมีฟีเจอร์ดีๆ มากมาย ทุกคนเลยอยากใช้ แต่การตัดสินใจแบบนี้มักเกิดขึ้นเสมอเมื่อมี ผลกระทบภายนอก
ผลกระทบภายนอกที่ผมให้ความสำคัญที่สุดคือ เวลาของผู้ใช้ บริษัทซอฟต์แวร์ส่วนใหญ่มักสนใจแค่ต้นทุนเวลาเชิงวิศวกรรม และมองข้ามเวลาของผู้ใช้ พวกเขาทุ่มเทกับการพัฒนาฟีเจอร์ แต่ไม่ค่อยปรับเวลาในการโต้ตอบของผู้ใช้ให้เหมาะสม ตัวอย่างเช่น ถ้าผมใช้เวลา 1 ชั่วโมงเพื่อทำให้แอปเร็วขึ้น 1 วินาที ผู้ใช้หนึ่งล้านคนจะประหยัดเวลาได้รวม 277 ชั่วโมงต่อปี แต่เพราะเวลาของผู้ใช้เป็นผลกระทบภายนอก การปรับแต่งแบบนี้จึงแทบไม่เกิดขึ้น
สุดท้ายผู้ใช้ก็ต้องดาวน์โหลดข้อมูลเกินความจำเป็นและรอโดยเปล่าประโยชน์ ขณะที่นักพัฒนาไม่ต้องรับผิดชอบต่อความสูญเปล่านั้น
ผมกำลังทำ Cargo/UV สำหรับภาษา C อยู่ เห็นด้วยกับบทความนี้มาก
ตอนเริ่มต้นใหม่ๆ การ ดูแล registry เป็นเรื่องยากมาก ไม่ใช่แค่ต้องเขียนโค้ด รักษาคุณภาพเครื่องมือ และขยายชุมชน แต่ยังต้องคิดเรื่องโครงสร้างพื้นฐานที่จะรองรับทราฟฟิกจากทั่วโลกด้วย ในสถานการณ์แบบนี้ โซลูชันที่อิง git จึงน่าดึงดูดมาก
แต่ปัญหาคือ sparse checkout ผมอยากใช้ git จัดการเวอร์ชันของ package manifest แต่ต้องติดตาม commit แบบตามอำเภอใจ ทำให้ไม่มีประสิทธิภาพ สุดท้ายเลยกลายเป็นโครงสร้างที่ต้อง push สอง commit ซึ่งในทางปฏิบัติแทบเป็นไปไม่ได้
ผมคิดว่าแนวทางของ Conan ใช้งานได้จริงที่สุด คือยอมแลก reproducibility แบบสมบูรณ์กับการใส่ตรรกะแบบมีเงื่อนไขไว้ใน manifest และยังทำ mapping ของ manifest ตามช่วงเวอร์ชันได้ด้วย ไม่สมบูรณ์แบบ แต่เป็น ทางประนีประนอมที่ใช้งานได้จริงและมีประโยชน์
แน่นอนว่าคำตอบที่แท้จริงคือใช้ฐานข้อมูล แต่ในโลกความจริงก็ไม่มีใครมาจ่ายค่าเซิร์ฟเวอร์และค่าดูแลแทนอยู่ดี
ถ้าปัญหาคือเรื่องเงินกับความเป็นอิสระ จะใช้แบบ P2P ก็ได้เหมือนกัน แต่ถ้าไม่มี CI caching ทราฟฟิกอาจพุ่งขึ้นมาก
โครงสร้าง mirror ของลินุกซ์ดิสโทรอย่าง Debian, Fedora, openSUSE ก็น่าศึกษาเช่นกัน
บทความนี้กำลังปนกันระหว่างสองปัญหา ปัญหาหนึ่งคือ การใช้ git เป็นฐานข้อมูลสำหรับ package index อีกปัญหาคือ การดึงโค้ดของแต่ละแพ็กเกจผ่าน git ซึ่งเป็นคนละเรื่องกัน
index จะใช้ git ส่วนแพ็กเกจจะใช้ zip/tar ก็ได้ หรือจะสลับกันก็ได้ สำหรับ Go นั้นเป็นโครงสร้างที่ไม่มี index ไปเลยด้วยซ้ำ
เรื่อง backend implementation ของ GitHub หรือจำนวน fork 20,000 อัน ไม่เกี่ยวกับแก่นของปัญหาเลย แม้ไม่มี git working tree ก็ยังทำ key-value lookup ที่มีประสิทธิภาพได้
ส่วนคำกล่าวที่ว่า “การ rewrite git history ก็เหมือน DB migration” ก็ดูแปลกๆ ไม่สู้ รัน Postgres ตัวเดียว ไปเลยไม่ดีกว่าหรือ?
แนวคิดแบบ “ใช้วิธีง่ายก่อนตอนที่มันยังใช้ได้ แล้วค่อยแก้ทีหลังถ้ามีปัญหา” ก็สมเหตุสมผล
Julia ก็ใช้แนวทางนี้เหมือนกัน และมีจำนวนแพ็กเกจน้อยกว่า Rust ราว 1/7 เลยยังไม่เป็นปัญหา
ปรับปรุงให้โหลดแค่ไฟล์ระดับบนสุด Registry.toml แล้วค่อยดาวน์โหลดเฉพาะแพ็กเกจที่ต้องใช้ก็ได้ ไม่ใช่ปัญหาใหญ่
วัฒนธรรม “Move fast and break things” ทำให้เราได้ซอฟต์แวร์ที่ช้าและบั๊กเยอะอย่างในปัจจุบัน
ผมเห็นด้วยกับข้อสรุปที่ว่า “Git เป็นฐานข้อมูลเริ่มต้นที่ยอดเยี่ยมสำหรับ package manager”
ผมอยู่ฝั่ง “สุดท้ายมันก็เวิร์กนี่” มุมมองคือมันช่วยการดำเนินงานระยะแรกได้มากพอ และปัญหาเรื่องสเกลก็ไปแก้ทีหลังได้
ตรงนี้มี survivorship bias อยู่ Cargo มีปัญหาเรื่อง git index โตขึ้นก็เพราะมันประสบความสำเร็จ
โปรเจ็กต์เล็กๆ ส่วนใหญ่ยังใช้ git เป็นโปรโตคอลกระจายข้อมูลได้ดีอยู่
ในช่วงเริ่มต้นที่ยังไม่รู้ว่าจะสเกลแค่ไหน การใช้ git และ GitHub เพื่อ โฟกัสกับปัญหาหลัก ถือว่า合理
เวลาเห็นบทความบนหน้าแรก HN ที่บอกว่า “สิ่งที่คุณทำอยู่ตอนนี้ผิด” ผมมักจะถ่อมตัวลงเสมอ
ผมเองก็เคยเจอแบบนั้นหลายครั้ง ครั้งนี้เป็นบทความเกี่ยวกับ PG Notify
แต่ตอนนี้ผมกำลังพัฒนาคนเดียว และยังไม่รู้ด้วยซ้ำว่าโปรเจ็กต์จะสำเร็จไหม ดังนั้นการ แจกจ่ายปลั๊กอินด้วย git จึงเป็นทางเลือกที่สมจริงที่สุด
ถึงอย่างนั้นถ้าวันหนึ่งมีปัญหาเรื่องสเกล ผมก็ตั้งใจจะกลับมาอ่านบทความนี้อีก
ส่วนตัวผมโฮสต์โค้ดด้วย Forgejo และป้องกันด้วย mTLS โดยไม่เปิดออกสู่ภายนอก
แต่ Go modules ต้องการใบรับรอง ทำให้มองไม่เห็นอินสแตนซ์ Forgejo ของผม
ถึงจะใช้ SSH ก็ยังบอกว่าต้องเข้าผ่าน HTTPS อยู่ดี สุดท้ายเลยต้องใช้ replace directive แล้วอ้างอิง clone ในเครื่องแทน ค่อนข้างยุ่งยาก
.gitที่ท้าย module path และตั้งค่า$GOPRIVATEก็จะใช้ การยืนยันตัวตนผ่านคำสั่ง git ได้โดยไม่ต้องมีคำขอ HTTPS ดู เอกสารทางการไม่ใช่แค่ package manager แต่โปรเจ็กต์เล็กๆ จำนวนมากก็ crowdsource ข้อมูลลงใน git repository เช่นกัน
ส่วนใหญ่มีขนาดเล็กพอจนยังไม่ชนข้อจำกัดทางเทคนิค
แต่โครงสร้างแบบนี้ทำให้ อุปสรรคในการมีส่วนร่วมของคนที่ไม่ใช่นักพัฒนา สูงขึ้น แม้ package manager จะเป็นข้อยกเว้น แต่กับโปรเจ็กต์ทั่วไปถือว่าเป็นปัญหา
ผมสร้างไลบรารีโอเพนซอร์สชื่อ Datatig เพื่อช่วยแก้ปัญหานี้
สไลด์ประกอบการบรรยายอยู่ ที่นี่ และจากนี้ไปผมก็ตั้งใจจะอ้างอิงบทความนี้เพื่อเพิ่มเนื้อหาเรื่อง การสเกล ด้วย