1 điểm bởi GN⁺ 2024-09-13 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong suốt 1 năm qua, tác giả đã nỗ lực tìm hiểu sâu cách vận hành ứng dụng Rails với SQLite sao cho có hiệu năng tốt và ổn định
  • Trong quá trình đó, đã rút ra nhiều bài học và muốn chia sẻ chúng
  • Bài viết sẽ giải thích nguyên nhân của các vấn đề và cách khắc phục

Vấn đề của SQLite và Rails

  • Theo mặc định, ứng dụng Rails dùng SQLite chưa ở trạng thái có thể dùng ngay
  • Với một chút điều chỉnh và tinh chỉnh, có thể tạo ra ứng dụng có hiệu năng tốt và ổn định
  • Trong Rails 8, mục tiêu là để cấu hình mặc định cũng đã sẵn sàng cho production

Ứng dụng demo "Lorem News"

  • Bài viết sẽ dùng ứng dụng demo tên là "Lorem News" để giải thích vấn đề và giải pháp
  • Ứng dụng này là bản sao của Hacker News, nơi người dùng có thể đăng bài viết và bình luận

Kiểm thử hiệu năng

  • Hiệu năng được kiểm thử bằng CLI load test oha và route benchmark bên trong ứng dụng
  • Hiệu năng được đo với cả một request đơn lẻ và nhiều request đồng thời

Vấn đề chính: ngoại lệ SQLITE_BUSY

  • SQLite dùng khóa ghi để chỉ cho phép một thao tác ghi tại một thời điểm
  • Khi nhiều kết nối cùng lúc cố lấy khóa ghi, ngoại lệ SQLITE_BUSY sẽ xảy ra
  • Để giải quyết vấn đề này, cần dùng immediate transaction

Immediate transaction

  • Mặc định, SQLite dùng chế độ deferred transaction
  • Khi dùng immediate transaction, hệ thống sẽ thử lấy khóa ghi ngay lập tức và có thể retry nếu thất bại
  • Có thể dùng gem sqlite3-ruby để đặt chế độ transaction mặc định thành immediate mode

Cấu hình timeout

  • Có thể giảm ngoại lệ SQLITE_BUSY bằng cách cấu hình timeout trong file database.yml
  • Có thể dùng thiết lập busy_timeout của SQLite để retry khi tranh chấp khóa ghi

Vấn đề GVL (Global VM Lock)

  • Gem sqlite3-ruby không giải phóng GVL khi gọi mã C của SQLite
  • Điều này làm giảm hiệu năng đồng thời
  • Có thể dùng busy_handler để giải phóng GVL và cải thiện hiệu năng

Cài đặt lại busy_timeout

  • busy_timeout được cài đặt lại để mọi query đều được retry với cùng một tần suất
  • Điều này giúp các query cũ không bị timeout

Cải thiện hiệu năng

  • Để cải thiện hiệu năng, cần áp dụng các cấu hình sau
    • dùng immediate transaction
    • cấu hình timeout
    • dùng busy_handler
    • dùng chế độ WAL (Write-Ahead Logging)
    • tách riêng connection pool đọc/ghi

Tóm tắt của GN⁺

  • Bài viết đề cập đến các vấn đề hiệu năng của ứng dụng Rails dùng SQLite và các cách khắc phục
  • Có thể cải thiện hiệu năng bằng các cách như immediate transaction, cấu hình timeout, giải phóng GVL, dùng chế độ WAL và tách riêng connection pool đọc/ghi
  • Bài viết này sẽ rất hữu ích cho các lập trình viên dùng SQLite và Rails
  • Với các dự án khác có chức năng tương tự, PostgreSQL và MySQL cũng là những lựa chọn được khuyến nghị

1 bình luận

 
GN⁺ 2024-09-13
Ý kiến trên Hacker News
  • Giới thiệu dự án Litestack của Oldmoe

    • Những người dùng SQLite và Rails nên xem dự án Litestack của Oldmoe
    • Litestack là một Ruby gem cung cấp hạ tầng dữ liệu cho ứng dụng web bằng cách tận dụng sức mạnh của SQLite
    • Nó cung cấp trong một gói duy nhất: cơ sở dữ liệu SQL, bộ nhớ đệm nhanh, hàng đợi tác vụ mạnh mẽ, message broker đáng tin cậy, công cụ tìm kiếm toàn văn và nền tảng metrics
    • Tôi đang dùng nó trong dự án hiện tại và rất hài lòng
  • Cảm ơn vì bài viết chi tiết

    • Đây là thông tin hữu ích cho những ai muốn mở rộng ứng dụng web dùng SQLite
    • Nội dung này cũng có thể áp dụng cho các framework khác ngoài Rails
    • Xin cảm ơn tác giả
  • Khuyến nghị cho những người làm việc với SQLite

    • Bất kỳ ai làm việc với SQLite đều nên đọc bài này, bất kể ngôn ngữ hay framework đang dùng
    • Bài viết đề cập đến những vấn đề mà vài năm trước người ta còn phải tự xử lý
    • Xin cảm ơn tác giả
  • Câu hỏi về hệ thống phân tích FOSS

    • Tôi đang xây dựng một hệ thống phân tích FOSS dễ cài đặt
    • Tôi muốn gửi dữ liệu sự kiện vào một cơ sở dữ liệu SQLite riêng để tách khỏi dữ liệu của ứng dụng chính
    • Tôi lo ngại về khả năng mở rộng để xử lý hơn 1000 sự kiện mỗi giây
    • Tôi đang cân nhắc cách lưu sự kiện trong bộ nhớ máy chủ rồi ghi theo lô mỗi giây một lần
    • Mong mọi người cho ý kiến liệu đây có phải là cách hợp lý để giải quyết nhiều vấn đề ghi DB của SQLite hay không
  • Vấn đề GVL của gem sqlite3-ruby

    • Gem sqlite3-ruby không giải phóng GVL khi gọi SQLite
    • Điều này có vẻ là một quyết định hợp lý trong đa số trường hợp
    • Có thể các phần mở rộng Python được thiết kế theo cách khác
    • Gem extralite giải phóng GVL trong lúc blocking, thường nhanh hơn và cũng không có vấn đề về đồng thời
  • Thiết lập cho dịch vụ web cá nhân

    • Một vài thiết lập tôi dùng trong dịch vụ web cá nhân:
      • PRAGMA journal_mode = WAL
      • PRAGMA busy_timeout = 5000
      • PRAGMA synchronous = NORMAL
      • PRAGMA cache_size = 1000000000
      • PRAGMA foreign_keys = true
      • PRAGMA temp_store = memory
      • sử dụng transaction BEGIN IMMEDIATE
  • Câu hỏi về Django

    • Bài viết này rất tuyệt
    • Tôi tò mò không biết có giải pháp tương tự nào cho Django hay không
    • ArchiveBox dùng SQLite thông qua Django và thường xuyên gặp các vấn đề được nhắc tới ở phía Rails
    • Sẽ rất tuyệt nếu có một giải pháp ở tầng SQLite mà không cần tuần tự hóa mọi lần ghi qua các kênh khác của ứng dụng
  • Thắc mắc về cấu hình mặc định của busy_timeout

    • Đây là một bài viết rất hữu ích và được viết rất tốt
    • Tôi thắc mắc vì sao phương thức busy_timeout mặc định lại có độ trễ kiểu trừng phạt các truy vấn cũ hơn
    • Tôi muốn hiểu vì sao thiết lập này lại hợp lý khi làm mặc định
  • Ý kiến về việc dùng SQLite với Rails

    • Tôi thích SQLite và Rails, nhưng điều này hơi giống như dùng MS Access trong môi trường production
  • Cảm ơn vì đã giải quyết vấn đề tích hợp Rails

    • Tôi luôn vui khi thấy ai đó giải quyết các vấn đề tích hợp và giúp đỡ người khác
    • Tôi hy vọng những chỉnh sửa này sẽ được đưa vào cấu hình Rails mặc định
    • Tôi đang vận hành ứng dụng Rails và đã chuyển sang Postgres vài năm trước, rất hài lòng với quyết định đó
    • Dù vậy, vẫn tốt khi có lựa chọn thay thế, và tôi vẫn dùng SQLite cho các công việc khác