1 คะแนน โดย GN⁺ 2024-09-13 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ตลอด 1 ปีที่ผ่านมา ผู้เขียนพยายามทำความเข้าใจอย่างลึกซึ้งว่าจะทำให้แอปพลิเคชัน Rails ที่ใช้ SQLite ทำงานได้ทั้งมีประสิทธิภาพและเสถียรอย่างไร
  • ในกระบวนการนี้ได้เรียนรู้บทเรียนหลายอย่าง และต้องการนำมาแบ่งปัน
  • บทความนี้จะอธิบายสาเหตุของปัญหาและวิธีแก้ไข

ปัญหาของ SQLite และ Rails

  • โดยพื้นฐานแล้ว แอปพลิเคชัน Rails ที่ใช้ SQLite ยังไม่พร้อมใช้งานได้ทันที
  • แต่ด้วยการปรับแต่งและจูนเล็กน้อย ก็สามารถสร้างแอปพลิเคชันที่ทั้งเร็วและเสถียรได้
  • ใน Rails 8 มีเป้าหมายให้การตั้งค่าเริ่มต้นเพียงอย่างเดียวก็พร้อมสำหรับใช้งาน production

เดโมแอปพลิเคชัน "Lorem News"

  • จะใช้งานเดโมแอปชื่อ "Lorem News" เพื่ออธิบายปัญหาและแนวทางแก้
  • แอปนี้เป็นโคลนของ Hacker News ที่ให้ผู้ใช้เขียนโพสต์และคอมเมนต์ได้

การทดสอบประสิทธิภาพ

  • ทดสอบประสิทธิภาพด้วย CLI สำหรับโหลดเทสต์ oha และเส้นทาง benchmarking ภายในแอปพลิเคชัน
  • วัดประสิทธิภาพทั้งจากคำขอเดี่ยวและคำขอพร้อมกันหลายรายการ

ปัญหาหลัก: ข้อยกเว้น SQLITE_BUSY

  • SQLite ใช้ write lock เพื่ออนุญาตให้มีงานเขียนได้เพียงครั้งละหนึ่งงาน
  • เมื่อมีหลาย connection พยายามแย่ง write lock พร้อมกัน จะเกิดข้อยกเว้น SQLITE_BUSY
  • เพื่อแก้ปัญหานี้ จำเป็นต้องใช้ immediate transaction

Immediate transaction

  • โดยปกติ SQLite จะใช้โหมด deferred transaction
  • หากใช้ immediate transaction ระบบจะพยายามขอ write lock ทันที และหากล้มเหลวก็สามารถ retry ได้
  • สามารถใช้ gem sqlite3-ruby เพื่อตั้งค่าโหมด transaction เริ่มต้นเป็น immediate mode ได้

การตั้งค่า timeout

  • สามารถลดข้อยกเว้น SQLITE_BUSY ได้ด้วยการตั้งค่า timeout ในไฟล์ database.yml
  • ใช้การตั้งค่า busy_timeout ของ SQLite เพื่อ retry การขอ write lock ได้

ปัญหา GVL (Global VM Lock)

  • gem sqlite3-ruby จะไม่ปล่อย GVL ระหว่างเรียกใช้โค้ด C ของ SQLite
  • สิ่งนี้ทำให้ประสิทธิภาพด้าน concurrency ลดลง
  • สามารถใช้ busy_handler เพื่อปล่อย GVL และปรับปรุงประสิทธิภาพได้

การ reimplement busy_timeout

  • มีการ reimplement busy_timeout เพื่อให้ทุก query ถูก retry ด้วยความถี่เท่ากัน
  • วิธีนี้ช่วยไม่ให้ query ที่เก่ากว่าหมดเวลา timeout

การปรับปรุงประสิทธิภาพ

  • เพื่อเพิ่มประสิทธิภาพ ควรใช้การตั้งค่าต่อไปนี้
    • ใช้ immediate transaction
    • ตั้งค่า timeout
    • ใช้ busy_handler
    • ใช้โหมด WAL (Write-Ahead Logging)
    • แยก connection pool สำหรับอ่าน/เขียน

สรุปโดย GN⁺

  • บทความนี้กล่าวถึงปัญหาด้านประสิทธิภาพของแอปพลิเคชัน Rails ที่ใช้ SQLite และแนวทางแก้ไข
  • สามารถปรับปรุงประสิทธิภาพได้ด้วยวิธีต่าง ๆ เช่น immediate transaction, การตั้งค่า timeout, การปล่อย GVL, การใช้โหมด WAL และการแยก connection pool สำหรับอ่าน/เขียน
  • บทความนี้มีประโยชน์มากสำหรับนักพัฒนาที่ใช้งาน SQLite และ Rails
  • สำหรับโปรเจ็กต์อื่นที่มีความสามารถคล้ายกัน ผู้เขียนแนะนำ PostgreSQL และ MySQL

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

 
GN⁺ 2024-09-13
ความคิดเห็นใน Hacker News
  • แนะนำโปรเจกต์ Litestack ของ Oldmoe

    • คนที่ใช้ SQLite และ Rails ควรลองดูโปรเจกต์ Litestack ของ Oldmoe
    • Litestack เป็น Ruby gem ที่ใช้พลังของ SQLite เพื่อมอบโครงสร้างพื้นฐานด้านข้อมูลสำหรับเว็บแอปพลิเคชัน
    • มีทั้งฐานข้อมูล SQL, แคชความเร็วสูง, คิวงานที่ทรงพลัง, message broker ที่เชื่อถือได้, เอนจินค้นหาข้อความแบบเต็ม, และแพลตฟอร์ม metrics อยู่ในแพ็กเกจเดียว
    • ตอนนี้กำลังใช้อยู่ในโปรเจกต์ปัจจุบันและพอใจมาก
  • ขอบคุณสำหรับบทความที่เขียนอย่างละเอียด

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

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

    • กำลังสร้างระบบ analytics แบบ FOSS ที่ติดตั้งง่าย
    • วางแผนจะแยกข้อมูล event ไปเก็บในฐานข้อมูล SQLite อีกตัว เพื่อแยกจากข้อมูลของแอปหลัก
    • กังวลเรื่องการขยายขนาดให้รองรับ event ได้มากกว่า 1000 รายการต่อวินาที
    • กำลังพิจารณาวิธีเก็บ event ไว้ในหน่วยความจำของเซิร์ฟเวอร์แล้วเขียนลงทีละชุดทุก ๆ 1 วินาที
    • ขอความเห็นว่านี่เป็นวิธีที่สมเหตุสมผลในการแก้ปัญหาการเขียน DB จำนวนมากของ SQLite หรือไม่
  • ปัญหา GVL ของ gem sqlite3-ruby

    • gem sqlite3-ruby ไม่ปล่อย GVL ระหว่างที่เรียก SQLite
    • ดูเหมือนว่านี่จะเป็นการตัดสินใจที่สมเหตุสมผลในกรณีส่วนใหญ่
    • ในส่วนขยายของ Python อาจออกแบบต่างออกไป
    • gem extralite จะปล่อย GVL ระหว่างที่บล็อกอยู่ และโดยทั่วไปเร็วกว่าโดยไม่มีปัญหาเรื่อง concurrency
  • การตั้งค่าสำหรับเว็บเซอร์วิสส่วนตัว

    • นี่คือการตั้งค่าบางอย่างที่ใช้ในเว็บเซอร์วิสส่วนตัว:
      • PRAGMA journal_mode = WAL
      • PRAGMA busy_timeout = 5000
      • PRAGMA synchronous = NORMAL
      • PRAGMA cache_size = 1000000000
      • PRAGMA foreign_keys = true
      • PRAGMA temp_store = memory
      • ใช้ทรานแซกชัน BEGIN IMMEDIATE
  • คำถามเกี่ยวกับ Django

    • บทความนี้ยอดเยี่ยมมาก
    • สงสัยว่ามีโซลูชันคล้ายกันสำหรับ Django หรือไม่
    • ArchiveBox ใช้ SQLite ผ่าน Django และเจอปัญหาแบบเดียวกับที่พูดถึงในฝั่ง Rails บ่อยครั้ง
    • ถ้ามีโซลูชันระดับ SQLite ที่ไม่ต้องทำให้การเขียนทั้งหมดถูก serialize ผ่านช่องทางอื่นของแอปก็คงดี
  • ข้อสงสัยเกี่ยวกับค่าตั้งต้นของ busy_timeout

    • เป็นบทความที่ให้ข้อมูลมากและเขียนได้ดีมาก
    • สงสัยว่าทำไมเมธอด busy_timeout แบบค่าตั้งต้นจึงมีการหน่วงเวลาที่เหมือนลงโทษ query ที่เก่ากว่า
    • อยากรู้ว่าทำไมสิ่งนี้จึงสมเหตุสมผลในฐานะค่าตั้งต้น
  • ความเห็นเกี่ยวกับการใช้ SQLite กับ Rails

    • ชอบ SQLite และ Rails แต่สิ่งนี้ก็คล้ายกับการใช้ MS Access ใน production
  • ขอบคุณสำหรับการแก้ปัญหาการรวมเข้ากับ Rails

    • ยินดีเสมอที่ได้เห็นการแก้ปัญหาการรวมระบบและช่วยเหลือผู้อื่น
    • หวังว่าการแก้ไขเหล่านี้จะถูกรวมเข้าไปในการตั้งค่าเริ่มต้นของ Rails
    • ดูแลแอป Rails อยู่ และเมื่อหลายปีก่อนได้ย้ายไป Postgres ซึ่งก็พอใจมาก
    • ถึงอย่างนั้น การมีทางเลือกอื่นก็ยังเป็นเรื่องที่ดี และยังใช้ SQLite กับงานอื่น ๆ อยู่