1 คะแนน โดย GN⁺ 3 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • หลังจาก lobste.rs เปลี่ยนจาก MariaDB มาใช้ SQLite ก็สามารถรับมือกับทราฟฟิกที่พุ่งสูงในวันจันทร์ได้อย่างเสถียร และพบว่าใช้ CPU·หน่วยความจำน้อยลง พร้อมการตอบสนองที่รู้สึกได้ว่าเร็วขึ้น
  • หากปิด VPS ของ MariaDB ที่แยกไว้ต่างหาก จะทำให้ ค่าใช้จ่าย VPS ลดลงครึ่งหนึ่ง และปิดภารกิจไมเกรชันที่เริ่มมาตั้งแต่ปี 2019 ได้สำเร็จ
  • การ deploy ครั้งแรก rollback เพราะแม้มีเฉพาะทราฟฟิกแบบอ่านอย่างเดียว CPU ทุกตัวก็แตะ 100% แต่หลังแก้ full table scan 2 จุดบนตารางขนาดใหญ่และปัญหา N+1 ก็ deploy ใหม่ได้สำเร็จ
  • ต้องแก้ข้อจำกัดด้าน ความเข้ากันได้ หลายอย่าง เช่น การทำ regexp·if·stddev ด้วย user-defined function ของ SQLite, การไม่รองรับ unsigned bigint และ NOCASE ที่จัดการได้เฉพาะ ASCII
  • การเปลี่ยนฐานข้อมูลพื้นฐานโดยไม่เข้าถึง production database ต้องอาศัย ข้อมูลทดสอบขนาดสมจริง, test suite, เช็กลิสต์ deploy·rollback และการทำงานร่วมกันอย่างใกล้ชิด

จากการหารือในปี 2019 สู่การเปลี่ยนมาใช้ SQLite

  • การหารือเรื่องไมเกรชันเริ่มขึ้นในปี 2019 จาก issue #539
    • ตอนนั้นมีการเสนอ MySQL เป็นทางเลือกโดยคำนึงถึงความเข้ากันได้กับ MariaDB แต่ก็มีการหารือเรื่องย้ายไป PostgreSQL อยู่ก่อนแล้ว
    • ในปี 2025 Rahul กล่าวถึง การเข้าซื้อ MariaDB โดย K1 ทำให้มีการหารือรายละเอียดการไมเกรตไป PostgreSQL ต่อเนื่อง
    • ในเดือนกุมภาพันธ์ Rahul ถามว่า lobsters สามารถรันบน SQLite ได้หรือไม่ และตั้งแต่เดือนมิถุนายน 2025 ก็เริ่มเข้าร่วมโปรเจกต์อย่างจริงจัง
  • PR แรก ในเดือนสิงหาคม 2025 หยุดค้างไป แล้วถูก GitHub ปิดในฐานะ PR เก่า จึงเปิดใหม่ไม่ได้
    • PR ที่สอง รวมการทดสอบประสิทธิภาพ, สคริปต์ไมเกรตข้าม DB ที่ทำขึ้นเอง และการดีบักด้านความถูกต้องครบถ้วนของข้อมูล
    • ในบรรดาสคริปต์แปลง MariaDB/MySQL→SQLite ที่มีอยู่ ไม่มีตัวใดตอบโจทย์ จึงเขียนสคริปต์แยกเอง

การ deploy ครั้งแรกล้มเหลวและการแก้คอขวดด้านประสิทธิภาพ

  • การ deploy ครั้งแรกวันที่ 21 กุมภาพันธ์ดำเนินไปพร้อม เช็กลิสต์การ deploy ที่เตรียมไว้ แต่ทันทีหลัง deploy แม้มีเพียงทราฟฟิกสถานะอ่านอย่างเดียว CPU ทุกตัวก็แตะ 100%
    • หาสาเหตุไม่พบจึง rollback
    • เนื่องจากไม่สามารถเข้าถึง production DB ได้ จึงยากที่จะจำลองปัญหาประสิทธิภาพจริงล่วงหน้า
  • สองวันหลังความล้มเหลว มีการเปิด PR ที่สามและเป็น PR สุดท้าย เพื่อแก้ปัญหาที่เกี่ยวกับการค้นหา และเพิ่ม สคริปต์สร้างข้อมูลจำนวนมาก ที่สร้างข้อมูลขนาดครึ่งหนึ่งของข้อมูล lobsters จริงในเครื่อง local
    • การสร้างข้อมูลใช้เวลา หนึ่งสัปดาห์
    • คอขวดของการ deploy ครั้งแรกคือ full table scan จากสอง query บนตารางขนาดใหญ่ และปัญหา N+1 อีกหนึ่งจุด
    • มีการนำ การเปลี่ยนแปลง 1, การเปลี่ยนแปลง 2, การเปลี่ยนแปลง 3 ที่แก้ปัญหานี้เข้าไป
    • ในเช้าวัน deploy ยังเพิ่ม slow query log เพื่อให้ติดตามปัญหาประสิทธิภาพเพิ่มเติมได้

ผลการ deploy ใหม่และการลดต้นทุนดำเนินงาน

  • ในการ deploy ครั้งที่สองวันที่ 11 กรกฎาคม มีการเตรียมเช็กลิสต์ deploy·rollback อีกครั้ง และหลังเปลี่ยนแล้วไซต์ยังทำงานตามปกติ โดยการใช้ CPU และหน่วยความจำคงที่อย่างเสถียร
    • ระหว่างเฝ้าดู metric ของไซต์และ IRC ปัญหาที่ถูกรายงานถูกจัดการทันทีด้วย การแก้ไข 1, การแก้ไข 2
    • แม้ทราฟฟิกพุ่งสูงในวันจันทร์ก็ไม่มีปัญหา และพบว่าใช้ CPU·หน่วยความจำน้อยลง พร้อมการตอบสนองที่รู้สึกได้ว่าเร็วขึ้น
    • หากปิด MariaDB VPS จะทำให้ ค่าใช้จ่าย VPS ลดลงครึ่งหนึ่ง และปิด issue เรื่องไมเกรชันได้

ข้อจำกัดของ SQLite ที่ต่างจาก MariaDB

  • ใช้การรองรับ user-defined function (UDF) ของ SQLite gem เพื่อทำ regexp, if, stddev ที่ SQLite ไม่มี
    • ทำให้ไม่ต้องเพิ่ม workaround เพื่อไมเกรต SQL มากเกินไป
  • SQLite ไม่รองรับ unsigned bigint ที่ MariaDB ใช้กับ ID บางส่วน จึงเปลี่ยนเป็น bigint
  • กฎการเรียงลำดับก็จำกัดกว่า MariaDB
    • ใน MariaDB ใช้ utf8mb4_general_ci แต่ใน SQLite ใช้ NOCASE
    • NOCASE ไม่รองรับการ case folding ของ UTF ทั้งหมด และ จัดการได้เฉพาะอักขระ ASCII
  • สำหรับตาราง full-text search ของ SQLite วิธีที่เหมาะคือ Contentless-Delete Tables ซึ่งเป็นวิธีที่แนะนำแต่ไม่ใช่ค่าเริ่มต้น
  • การตั้งค่า PRAGMA เริ่มต้น ของ Rails ทำงานได้ตามปกติใน lobsters
    • database migration แตกต่างกันไปตามแต่ละ DB จึงย้าย migration เดิมไปยังไดเรกทอรี old_migrations และทำให้ db:migrate ยังทำงานต่อได้

บทเรียนจากการทดสอบและการทำงานร่วมกัน

  • ใน codebase ของ lobsters พบ search parser และ heinous_inline_partials ซึ่งเป็นแฮ็กเพื่อเพิ่มความเร็วในการ render
    • ด้วย test suite จึงสามารถตรวจสอบความถูกต้องของการย้ายไป SQLite ได้โดยไม่ต้องทดสอบด้วยมือครั้งใหญ่
  • การสื่อสารที่ราบรื่นระหว่างผู้มีส่วนร่วมเป็นตัวตัดสินความสำเร็จของไมเกรชัน
    • การเปลี่ยนฐานข้อมูลพื้นฐานในสภาพที่เข้าถึง production DB ไม่ได้นั้นยากมาก
    • หากต้องทำงานแบบเดียวกันอีกครั้ง ควรเตรียม ขนาดข้อมูลที่สมจริง ไว้ล่วงหน้า
  • หากตั้งค่าให้การทดสอบล้มเหลวเมื่อเกิด full table scan ได้ ก็น่าจะจับปัญหาประสิทธิภาพในการ deploy ครั้งแรกได้ล่วงหน้า
  • จำเป็นต้องมีเครื่องมือที่สามารถสร้างชุดข้อมูลคล้าย production ได้ง่าย โดยไม่ต้องเขียนเองหรือรอนานเป็นสัปดาห์

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

 
GN⁺ 3 시간 전
ความคิดเห็นใน Lobste.rs
  • @thomas0 รับหน้าที่ดูแลการไมเกรตครั้งใหญ่ได้อย่างละเอียดรอบคอบ, @355E3B ช่วยเรื่องการวางแผนและการดำเนินงาน และด้วยผู้ร่วมพัฒนาที่เกี่ยวข้องกับ #539 กับ #lobsters ทำให้ช่วงเวลาแบบอ่านอย่างเดียวผ่านไปได้อย่างสนุก
    เดิมทีใน #539 วางแผนจะใช้ PostgreSQL แต่ผู้ที่อาสามาช่วยลงมือทำจริงใช้ SQLite และไม่อยากใช้โซลูชันที่ใหญ่และซับซ้อนเกินความต้องการที่คาดไว้ จึงเลือก SQLite โดยปกติ PostgreSQL เป็นตัวเลือกเริ่มต้น แต่ก็มีภาระที่ต้องรัน ปรับแต่ง และดูแลบริการแยกต่างหาก
    น่าประหลาดใจที่หลังไมเกรตแล้ว การใช้ CPU และ RAM ลดลงทั้งคู่ โดยมีกราฟรายละเอียดอยู่ที่ ช่วงท้ายของ #539

  • ทุกครั้งที่เห็นค่าเริ่มต้นของ SQLite ก็รู้สึกแปลกใจ อยากให้มีโหมดสำหรับโปรเจกต์ใหม่ที่เปิดใช้ WAL, foreign key, synchronous=NORMAL ฯลฯ เป็นค่าเริ่มต้น และน่าจะยังมีค่าเริ่มต้นอื่น ๆ ที่ควรปรับปรุงได้อีก

    • จำได้เลือน ๆ ว่ายังมีส่วนที่การกำหนดเวลา/รูปแบบของการเปลี่ยนแปลงบนดิสก์นั้น เหมาะกับ HDD แต่ค่อนข้างไม่เหมาะกับ NVMe อยู่ด้วย
  • สงสัยว่า กลยุทธ์การสำรองข้อมูล ของฐานข้อมูล SQLite เป็นอย่างไร อยากรู้ว่าใช้เครื่องมืออย่าง litestream หรือใช้วิธีอื่น

  • สงสัยว่า lobste.rs จัดการ งานอัปเดต อย่างไร

    • ใช้คำสั่ง update ของ SQLite ถ้าหมายถึงการอัปเดตประเภทอื่น ขอให้ระบุให้ชัดเจน
  • SQLite มี ฟังก์ชัน iif อยู่แล้ว แต่การรัน ฟังก์ชันที่ผู้ใช้กำหนดเอง ในบริเวณเดียวกันนั้นก็น่าสนใจเช่นกัน

  • เคยคิดว่า SQLite ไม่รองรับ concurrency ในระดับเดียวกับ PostgreSQL หรือ MySQL แต่เมื่อดู เอกสารภาพรวม แล้ว ควรเข้าใจว่าตอนนี้สามารถใช้จากหลายโปรเซสได้ แต่ lobste.rs ต้องรันบนเซิร์ฟเวอร์เดียวใช่ไหม
    อยากรู้ด้วยว่าโปรเซสต่าง ๆ ซิงโครไนซ์กันภายใน SQLite ด้วยวิธีอย่าง shared memory หรือ mmap ทำให้เขียนพร้อมกันได้เพียงรายการเดียวแต่เปิดให้อ่านได้หลายรายการหรือไม่ และงานเขียนที่รออยู่จะรอคิวตามลำดับหรือไม่

    • SQLite FAQ น่าจะตอบข้อสงสัยบางส่วนได้ สามารถทำ งานอ่านหลายรายการ ข้ามหลายโปรเซสได้ แต่การเขียนทำได้ครั้งละหนึ่งรายการเท่านั้น และ SQLite รับประกันสิ่งนี้ด้วยการล็อกของระบบไฟล์
      สำหรับการรอเขียน ดูเหมือนว่าจะไม่ได้ใช้คิว shared memory แบบชัดเจน แต่ใช้วิธีรอสั้น ๆ แล้วลองใหม่ ไม่แน่ใจว่าการใช้ SQLite จากหลายเครื่องเป็นไปไม่ได้โดยสิ้นเชิงหรือไม่ แต่แนวทางง่าย ๆ อย่างการแชร์ไฟล์ผ่าน NFS นั้นไม่แนะนำอย่างชัดเจน ดังนั้นการเข้าใจว่าเป็น เซิร์ฟเวอร์เดียว จึงเป็นการประมาณที่สมเหตุสมผล และสมัยนี้เครื่องเดียวก็รองรับสเกลได้ค่อนข้างมากแล้ว
    • มีการให้การเขียนรออยู่บ้าง แต่ ค่าเริ่มต้นไม่ดีนัก จึงต้องปรับแต่ง ค่าที่ใช้อยู่ตอนนี้เป็นดังนี้ และต้องเปิดโหมด immediate transaction ด้วยเพื่อให้ busy_timeout ทำงาน
      "PRAGMA foreign_keys=ON;"  
      "PRAGMA journal_mode = WAL;"  
      "PRAGMA synchronous = NORMAL;"  
      "PRAGMA busy_timeout = 5000;"  
      "PRAGMA temp_store = MEMORY;"  
      "PRAGMA mmap_size = 134217728;"  
      "PRAGMA journal_size_limit = 67108864;"  
      "PRAGMA cache_size = 2000;"  
      
      แหล่งอ้างอิงคือ https://fractaledmind.github.io/2024/04/… และ https://kerkour.com/sqlite-for-servers รวมถึง https://www.sqlite.org/lang_transaction.html#immediate โดยดูรายละเอียดได้จากส่วน SQLITE_BUSY ของลิงก์ที่สองและจากลิงก์ที่สาม
  • เป็นการอัปเกรดที่น่าประทับใจ ซึ่งมีการ整理รายละเอียดไว้ดีมากใน PR, issue และ Gist สงสัยว่าการเปลี่ยนแปลงนี้ทำให้เกิด ความแตกต่างด้านฟังก์ชัน อย่างการค้นหาในไซต์หรือไม่ และขนาดบนดิสก์ของไฟล์ SQLite ปัจจุบันเท่าไร

    • ผลการค้นหาใช้ ฟีเจอร์การจัดอันดับ ของ SQLite จึงอาจต่างจากลำดับผลลัพธ์ของ MariaDB มี เทสต์การค้นหา อยู่ แต่มีการตรวจสอบอันดับน้อย จึงมีความเป็นไปได้ว่าจะมีการเปลี่ยนแปลงด้านฟังก์ชัน แต่ยังไม่มีหลักฐาน
      จำได้ว่าเคยมีบอกไว้ที่ไหนสักแห่งว่าไฟล์ SQLite มีขนาด 3.8GB