5 คะแนน โดย GN⁺ 2025-06-07 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • GitLab พบปัญหาว่าการแบ็กอัปรีโพขนาดใหญ่ใช้เวลานานมาก และได้ดำเนินการปรับปรุง
  • สาเหตุหลักคือฟังก์ชัน Git ที่มีอายุ 15 ปีซึ่งมี ความซับซ้อนแบบ O(N²) และได้เพิ่มประสิทธิภาพอย่างมากผ่านการปรับอัลกอริทึม
  • หลังการปรับปรุง เวลาแบ็กอัปของรีโพที่ใหญ่ที่สุดลดลงจาก 48 ชั่วโมงเหลือ 41 นาที
  • วิธีที่ปรับปรุงแล้วให้ทั้งประสิทธิภาพการใช้ทรัพยากรและความน่าเชื่อถือ พร้อมส่งผลดีต่อ เครื่องมือที่ใช้ Git และคอมมูนิตี้ อื่น ๆ ด้วย
  • ตั้งแต่ GitLab เวอร์ชัน 18.0 เป็นต้นไป ผู้ใช้ทุกคนจะได้รับประโยชน์เหล่านี้โดยไม่ต้องตั้งค่าเพิ่มเติม

ความสำคัญและความท้าทายของการแบ็กอัปรีโพ

  • การแบ็กอัปรีโพเป็นองค์ประกอบหลักของ กลยุทธ์การกู้คืนยามฉุกเฉิน
  • ยิ่งรีโพมีขนาดใหญ่ขึ้น ความซับซ้อนของ กระบวนการแบ็กอัปที่เชื่อถือได้ ก็ยิ่งเพิ่มขึ้น
  • รีโพ Rails ภายในของ GitLab ใช้เวลาแบ็กอัปนานถึง 48 ชั่วโมง ทำให้ต้องเลือกระหว่าง ความถี่ในการแบ็กอัปกับประสิทธิภาพของระบบ
  • มีปัญหาหลากหลายในรีโพขนาดใหญ่ เช่น เวลา ทรัพยากร ความเสี่ยงต่อความล้มเหลว และ race condition
    • ส่งผลให้แต่ละองค์กรต้องใช้กลยุทธ์ที่ไม่สม่ำเสมอ เช่น ตั้งเวลาแบ็กอัปลำบาก พึ่งพาเครื่องมือภายนอก หรือทำแบ็กอัปน้อยลง

การวิเคราะห์คอขวดด้านประสิทธิภาพและการหาสาเหตุ

  • ฟีเจอร์แบ็กอัปของ GitLab ใช้คำสั่ง git bundle create เพื่อสร้างสแนปชอตของรีโพ
  • ในกระบวนการทำงานของคำสั่งนี้เกิดคอขวดด้านประสิทธิภาพเมื่อจำนวน reference เพิ่มขึ้น
  • มีกรณีที่การแบ็กอัปรีโพขนาดใหญ่ซึ่งมี reference หลายล้านรายการใช้เวลามากกว่า 48 ชั่วโมง

การวิเคราะห์สาเหตุ

  • ใช้การวิเคราะห์ Flame Graph ระหว่างรันคำสั่งเพื่อตรวจสอบจุดคอขวด
  • เมื่อมี reference 10,000 รายการ เวลาทำงานประมาณ 80% ถูกใช้ไปกับฟังก์ชัน object_array_remove_duplicates()
  • ฟังก์ชันนี้ถูกเพิ่มเข้ามาในปี 2009 ผ่านคอมมิตนี้ เพื่อจัดการ reference ที่ซ้ำกัน
    • ป้องกันปัญหาที่อาจเกิดขึ้นเมื่อใช้ git bundle create แล้วมี reference ซ้ำอยู่
    • แต่การตรวจหาค่าซ้ำถูกเขียนเป็น nested for loop จึงมีความซับซ้อนแบบ O(N²)
    • ยิ่งจำนวน reference มาก คอขวด ก็ยิ่งรุนแรงขึ้น

เปลี่ยนจาก O(N²) ไปสู่การแมปที่มีประสิทธิภาพ

  • GitLab ได้ส่งแพตช์ให้ Git โดยเปลี่ยนจาก nested loop มาใช้ โครงสร้างข้อมูลแบบ map
    • เพิ่มแต่ละ reference ลงใน map เพื่อกำจัดค่าซ้ำโดยอัตโนมัติและประมวลผลได้อย่างมีประสิทธิภาพ
  • การเปลี่ยนแปลงนี้ทำให้ประสิทธิภาพของ git bundle create ดีขึ้นอย่างมาก และขยายขีดความสามารถได้แม้ในสภาพแวดล้อมที่มี reference จำนวนมาก
  • ผลเบนช์มาร์กยืนยันว่า ที่ระดับ reference 100,000 รายการ ประสิทธิภาพดีขึ้น มากกว่า 6 เท่า

เวลาแบ็กอัปที่ลดลงอย่างมากและผลลัพธ์ที่ได้

  • เวลาแบ็กอัปสูงสุดของรีโพลดลงจาก 48 ชั่วโมงเหลือ 41 นาที (เหลือเพียง 1.4%)
  • สามารถให้ ประสิทธิภาพที่สม่ำเสมอ ได้โดยไม่ขึ้นกับขนาดของรีโพ
  • ได้ประโยชน์เพิ่มเติม เช่น ภาระบนเซิร์ฟเวอร์ลดลง และ ประสิทธิภาพที่ดีขึ้น ของคำสั่ง Git อื่น ๆ ที่อาศัยการแบ็กอัป
  • แพตช์นี้ถูกรวมเข้า upstream Git แล้ว และ GitLab ก็ใช้งานทันทีเพื่อให้ลูกค้าได้รับประสบการณ์ที่เร็วขึ้น

การเปลี่ยนแปลงที่เกิดขึ้นจริงสำหรับลูกค้า GitLab

  • ลูกค้าองค์กรขนาดใหญ่สามารถรัน การแบ็กอัปข้ามคืนต่อเนื่อง ได้โดยไม่ชนกับเวิร์กโฟลว์การพัฒนา
  • ด้วย Recovery Point Objective (RPO) ที่สั้นลง ความเสี่ยงของการสูญเสียข้อมูลในกรณีเกิดภัยพิบัติจึงลดลงอย่างมาก
  • โอเวอร์เฮดด้านปฏิบัติการ เช่น การใช้ทรัพยากร เวลาบำรุงรักษา และ ค่าใช้จ่ายบนคลาวด์ ก็ลดลงด้วย
  • แม้ขนาดรีโพจะเพิ่มขึ้น ก็ไม่ต้องกังวลอีกต่อไประหว่าง ความถี่ในการแบ็กอัปกับประสิทธิภาพของระบบ
  • ตอนนี้ผู้ใช้ GitLab ทุกคนสามารถรับประโยชน์เหล่านี้ได้ โดยไม่ต้องเปลี่ยนคอนฟิก

ขั้นตอนถัดไปและความหมายของเรื่องนี้

  • การปรับปรุงครั้งนี้เป็นส่วนหนึ่งของความพยายามต่อเนื่องของ GitLab ในการสร้าง โครงสร้างพื้นฐาน Git ระดับองค์กรที่ขยายขนาดได้ดี
  • การเปลี่ยนแปลงที่ GitLab มีส่วนร่วมถูกนำกลับไปยังโปรเจกต์ upstream Git ด้วย ทำให้เกิดผลกระทบเชิงบวกต่อ ทั้งอุตสาหกรรมและคอมมูนิตี้โอเพนซอร์ส
  • ความพยายามในการปรับปรุงโครงสร้างพื้นฐานลักษณะนี้กำลังกลายเป็นต้นแบบให้กับงาน ปรับปรุงประสิทธิภาพหลัก อื่น ๆ

เรื่องราวเชิงลึกเพิ่มเติมเกี่ยวกับแนวทางด้านประสิทธิภาพของ GitLab สามารถดูได้ในงาน GitLab 18 virtual launch event

1 ความคิดเห็น

 
GN⁺ 2025-06-07
ความคิดเห็นจาก Hacker News
  • มีการแชร์ข้อมูลว่าโค้ดปรับปรุงประสิทธิภาพที่ GitLab มีส่วนร่วมกับ Git มีกำหนดปล่อยใน v2.50.0 ลิงก์คอมมิตที่เกี่ยวข้อง

  • จากประสบการณ์ตรง ย้ำว่าการตัดการคำนวณแบบ n^2 ออกจากโค้ดที่เขียนเองเป็นทางเลือกที่ถูกต้องเสมอ และรู้สึกแปลกใจที่แม้ไม่ต้องเขียนอัลกอริทึมพิเศษ ปัญหาก็มักเผยให้เห็นได้ง่ายแม้ที่ค่า n เล็กมาก

    • อ้างถึงกฎข้อแรกของการคอมพิวต์ของ Bruce Dawson: O(n^2) คือจุดที่ก่อปัญหาด้านการขยายระบบที่เลวร้ายที่สุด มันเร็วพอในโปรดักชัน แต่พอปล่อยใช้งานจริงกลับเกิดการเสื่อมประสิทธิภาพอย่างร้ายแรง ลิงก์บทความที่เกี่ยวข้อง

    • แชร์ประสบการณ์ส่วนตัวว่าเคยเห็นหลายครั้งที่ time complexity แบบ O(N^2) ดูเร็วในช่วงทดสอบ แต่พอขึ้นโปรดักชันแล้วเกิดปัญหารุนแรง

    • จากประสบการณ์ของตน ปัญหาส่วนใหญ่ (80~90%) ถ้าต้องใช้อัลกอริทึมซับซ้อน มักเป็นสัญญาณว่า data model เองไม่ถูกต้อง คิดว่ามีเพียงบางกรณีพิเศษ เช่น คอมไพเลอร์ ภายใน DB หรือการวางแผนเส้นทาง ที่จำเป็นต้องใช้อัลกอริทึมซับซ้อนโดยเนื้อแท้

    • กล่าวถึงข้อยกเว้นเฉพาะกรณีฮาร์ดแวร์ขนาดเล็กที่ n น้อยกว่า 10 เท่านั้น (เช่น CAN interface) แต่ถ้า n มีโอกาสเพิ่มขึ้นได้โดยไม่เปลี่ยนฮาร์ดแวร์ ก็ต้องหลีกเลี่ยงการคำนวณแบบ n^2 อย่างเด็ดขาด และควรจำกัดไว้ที่ระดับการออกแบบหรือมีการตรวจจับล่วงหน้าเพื่อรับรู้ว่าจำเป็นต้องออกแบบใหม่

    • เจ้าตัวระบายความอัดอั้นว่ากำลังเจอคอขวดจากการคำนวณแบบ n^3 ที่ทำให้ติดขัดแม้มีเพียง 10,000 องค์ประกอบ และยังหาวิธีแก้ไม่ได้

  • มองว่าเป็นการค้นพบที่น่าสนใจ พร้อมแชร์ความเสียดายว่าต่อให้เนื้อหาสั้นลงเหลือเพียง 1/10 ก็น่าจะสื่อสารได้มีประสิทธิภาพพออยู่ดี อย่างไรก็ดี ก็ยังมีข้อดีตรงที่ไม่ใช่คอนเทนต์วิดีโอจึงกวาดอ่านได้ง่าย

    • แนะนำสำหรับคนที่ยังไม่ได้อ่านบทความว่า ถ้าอยากจับประเด็นสำคัญให้คุ้มที่สุด ให้อ่านถึงช่วงหลัง flame graph แล้วหยุดก่อนถึงตอนพูดเรื่อง backport

    • รู้สึกว่าสไตล์ของบทความทั้งชิ้นยาวมากจนเหมือนข้อความที่ LLM (โมเดลภาษาขนาดใหญ่) สร้างขึ้น และนั่นเป็นสิ่งที่ติดอยู่ในความทรงจำ

    • คิดว่าต่อให้บทความยาวกว่านี้ก็ยังไม่เป็นไร และยังสงสัยอยู่ดีว่าทำไมถึงสร้าง backup bundle สองชุดจาก ref

  • รู้สึกว่าการใช้เวลาถึง 48 ชั่วโมงในการบีบอัดโฟลเดอร์ git ขนาดหลาย GB นั้นนานมาก และแม้แต่ 41 นาทีก็ยังถือว่านานอยู่ สงสัยว่าทำไมไม่ snapshot/archive ทั้ง repo ไปเลย แต่ต้องใช้ git bundle โดยเฉพาะ และอยากรู้ว่า git bundle มีข้อดีอะไรเหนือกว่าแบ็กอัป ZFS ที่ทำเป็นประจำ

    • ใน FAQ ทางการของ Git ระบุว่าวิธีแบบนี้มีความเสี่ยง เพราะเป็นการข้ามขั้นตอนตรวจสอบความถูกต้องตามปกติของ Git จึงแนะนำให้ใช้ git fsck เพื่อตรวจสอบความสมบูรณ์ของคอลเลกชัน ในระดับบุคคล แค่ Syncthing กับ Btrfs snapshot ก็เร็วและเสถียรพอแล้ว เอกสารที่เกี่ยวข้อง

    • มีการพูดถึงว่าการนำ ZFS snapshot ไปทำ offsite backup บนระบบที่ไม่ใช่ฐาน ZFS อย่าง S3 มีข้อจำกัด และหนึ่งในความสามารถที่ไม่ค่อยมีคนรู้ของ git bundle คือสามารถระบุตำแหน่งให้ git clone --bundle-uri ได้ หากเซิร์ฟเวอร์บอกตำแหน่ง bundle ให้ไคลเอนต์ ไคลเอนต์ก็จะดึง bundle มาแตกอย่างรวดเร็ว แล้วค่อยรับ delta update จากเซิร์ฟเวอร์เท่านั้น ทำให้ภาระในการโคลน repo ขนาดใหญ่ลดลงมาก

    • มองว่าสุดท้ายแล้วก็คือการเพิ่มแคชในจุดที่ต้องมีแคช โดยปกติ repo ของ git มีลักษณะเป็นระบบกระจายอยู่แล้ว จึงสงสัยว่าแค่ mirror ไปยัง repository อื่นและจัดการด้วยเครื่องมือ snapshot/backup ของ filesystem ก็น่าจะพอไม่ใช่หรือ ประเด็นสำคัญคือข้อมูล version control ที่สำคัญควรถูกกระจายอยู่แล้ว

  • แม้จะไม่มีประสบการณ์กับการแบ็กอัป git มากนัก แต่ก็สงสัยว่าทำไมการสร้างแบ็กอัปจาก local repo โดยตรงถึงทำให้เกิด race condition ได้

  • ถ้าปัญหายุ่งยากถึงระดับนี้เพราะ data protocol ชั้นบน ก็สงสัยว่าทำไมไม่ใช้ block-level snapshot ไปเลย แม้ Git จะไม่มี WAL (Write Ahead Logging) เหมือนเป็นอุปสรรค แต่กับ SQLite แค่เพิ่ม WAL mode ก็สามารถใช้กลยุทธ์ block snapshot ได้อย่างสะดวกในสภาพแวดล้อมจริง และคิดว่าถ้า Git มีสถาปัตยกรรมแบบนี้ก็น่าจะวางกลยุทธ์แบ็กอัปที่เสถียรกว่านี้ได้มาก

    • เห็นด้วยว่าปัญหาส่วนหนึ่งมาจาก Git ไม่มีเมกานิซึมคล้าย WAL และแชร์ประสบการณ์ว่าเคยเชื่อ snapshot อย่างเดียวจนตอนกู้คืนพบว่า repository เสียหาย ซึ่งเป็นปัญหาร้ายแรง แม้จะกู้ได้แต่ยุ่งยากมาก

    • แชร์ทิปว่าใน SQLite ช่วงหลังมีทางเลือกที่ดีกว่าแล้วคือโซลูชัน sqlite3_rsync

    • อธิบายมุมของ GitLab ว่าไม่ใช่แค่บริการ managed service อย่างเดียว แต่เป็นผลิตภัณฑ์ที่ผู้ใช้สามารถติดตั้งเองและนำไปใช้ในสภาพแวดล้อมได้หลากหลาย ดังนั้น filesystem, การรองรับ snapshot และเงื่อนไขของระบบปฏิบัติการของผู้ใช้แต่ละรายจึงต่างกัน กล่าวคือ GitLab ต้องการระบบแบ็กอัปแบบอิสระที่ทำงานได้ทั่วไปในทุกสภาพแวดล้อม

  • เห็นข้อความว่า "ลดเวลาแบ็กอัปแบบ exponential ด้วยการเปลี่ยนอัลกอริทึม" แล้วก็สงสัยว่าหมายถึงลดจาก O(n^2) ไปเป็น O(n^2/2^n) หรือไม่ ซึ่งเดาว่าคงไม่ใช่แบบนั้นจริง

    • อธิบายว่าความซับซ้อนของอัลกอริทึมในฟังก์ชันที่แก้จริง ๆ เร็วขึ้น 6 เท่า และในบริบทการใช้งานอื่น ๆ ทำให้เวลาทำงานรวมลดเหลือ 1% ซึ่งในกรณีนี้การเรียกว่า "พัฒนาขึ้นแบบ exponential" ในเชิงการตลาดก็พอเข้าใจได้ ส่วนการนิยามความซับซ้อนจริง ๆ อาจไม่ได้มีความหมายมากนัก

    • ชี้ชัดว่าในการสนทนาทั่วไป คำว่า "exponential" ไม่ได้ถูกใช้ในความหมายทางคณิตศาสตร์แบบเป๊ะ ๆ แต่เป็นการเปรียบเปรยว่า "ดีขึ้นมาก"

    • ตามความเข้าใจของตน คิดว่าอาจหมายถึงลดจาก n ลงไปถึง log(n) คล้ายกับที่มักอธิบายว่า quantum Fourier transform เร็วกว่า DFT แบบเดิมอย่าง exponential โดยยกสถานการณ์ที่ความซับซ้อนเปลี่ยนจาก n^2 เป็น nlogn

    • ถ้าแทนอัลกอริทึม n^2 ด้วยการ lookup แบบ log(n) ก็ถือว่าเร็วขึ้นแบบ exponential จริง แต่ในทางปฏิบัติมักไปได้ถึง O(1) อย่างการ lookup ใน hash map จึงเร็วกว่าเสียอีก

    • คิดว่าการถกเถียงเรื่องนี้เองเป็นการจับผิดที่ไม่ก่อประโยชน์

  • มีความเห็นว่านี่เป็นตัวอย่างที่ดีว่าการเขียนด้วย C ไม่ได้แปลว่าจะได้ประสิทธิภาพสูงเสมอไป และโครงสร้างข้อมูลกับอัลกอริทึมสำคัญกว่า

    • C ทำให้การสร้างคอนเทนเนอร์ที่เหมาะสมเป็นเรื่องยากมาก จึงเกิดปัญหาประสิทธิภาพแบบนี้บ่อยกว่า ส่วน C++ หรือ Rust มีโครงสร้างข้อมูลในตัวอย่าง unordered_set/HashSet ทำให้นักพัฒนาไม่ปล่อยผ่านด้วย for loop ง่าย ๆ แต่มีแนวโน้มจะ optimize อย่างเป็นธรรมชาติมากกว่า และในกรณีนี้แม้ Git เองจะมี string set อยู่แล้ว แต่ไม่ใช่สิ่งที่ใช้กันเป็นมาตรฐาน จึงวิเคราะห์ว่าผู้เขียนเดิมอาจไม่รู้ว่ามีอยู่
  • กล่าวถึงบทเรียนว่าต้องหาสมดุลระหว่างการ optimize ก่อนเวลาอันควรกับการ optimize แบบคาดการณ์ล่วงหน้า โดยทั่วไปมักเตือนให้ระวังการ optimize เร็วเกินไป แต่ก็เสนอเป็นกฎว่า ถ้าเป็นฟังก์ชันที่ถูกเรียกบ่อยมากและมีการปรับปรุงที่ชัดเจน ทำได้ง่าย ก็ควรใส่ไว้ล่วงหน้า

    • คาดว่าถ้าภาษาต้นทางตอน implement มี set-of-strings ในตัว นักพัฒนาเดิมก็น่าจะใส่ optimization แบบนี้ได้ง่าย สุดท้ายจึงมองว่าปัญหาแบบนี้มีรากมาจากข้อจำกัดเชิงโครงสร้างของภาษาอย่าง C ที่มีคอนเทนเนอร์น้อย
  • ส่งต่อลิงก์คอมมิตที่เกี่ยวข้องโดยตรง (รายละเอียดการปรับปรุงอัลกอริทึม) ลิงก์คอมมิตที่เกี่ยวข้อง