2 điểm bởi GN⁺ 2023-11-08 | 1 bình luận | Chia sẻ qua WhatsApp
  • PR refactor PDS #1705 của Bluesky atproto thay đổi PDS sang sử dụng kho dữ liệu SQLite đơn tenant, đồng thời lưu repo của từng người dùng và trạng thái tài khoản riêng tư trong các tệp SQLite riêng của họ
  • DB người dùng được lưu theo cấu trúc đường dẫn /${dbDirectory}/${sha256Hex(did).slice(0,2)}/${did}, và khóa ký của mỗi repo được lưu ngay cạnh tệp SQLite tương ứng
  • Lớp trừu tượng truy cập dữ liệu người dùng hiện tại được thay bằng ActorStore; do SQLite không hỗ trợ transaction đồng thời, các thao tác ghi phải liên kết tường minh với store và transaction
  • Các file handle DB đang mở và khóa ký được quản lý bằng LRUCache; tối đa giữ 30k file handle đang mở và 30k khóa trong bộ nhớ, và khi DB bị đẩy khỏi cache thì file handle sẽ được đóng
  • Ba DB SQLite riêng được đưa vào để quản lý trạng thái dịch vụ và chạy ở chế độ WAL nhằm cho phép đọc đồng thời và sao chép streaming; bản phân phối PDS dự kiến sẽ kèm Litestream hoặc công cụ tương tự

Các thay đổi chính của PR

  • PR #1705 refactor PDS dựa trên kho dữ liệu SQLite đơn tenant
  • Mỗi người dùng có một tệp SQLite chuyên dụng của riêng mình, trong đó lưu repo và trạng thái tài khoản riêng tư của người dùng đó
  • DB người dùng được lưu theo đường dẫn phân cấp sử dụng hash của DID
    • Định dạng đường dẫn: /${dbDirectory}/${sha256Hex(did).slice(0,2)}/${did}
  • repo signing key của mỗi repo được lưu cùng vị trí với tệp SQLite

ActorStore và mô hình transaction

  • Lớp trừu tượng truy cập dữ liệu người dùng được đổi từ “services” hiện có sang ActorStore
  • Điểm khác biệt chính của ActorStore là tách riêng các lớp cho đọc và ghi
  • Vì SQLite không hỗ trợ transaction đồng thời, để thực hiện thao tác ghi cần phải liên kết rõ ràng với store và transaction
  • Log commit bao gồm việc làm lại reader và transactor, xử lý race transaction của actor store, dọn dẹp interface của store, v.v.

Quản lý cache và file handle

  • LRUCache được duy trì cho khóa ký và cơ sở dữ liệu
  • Các giới hạn được cấu hình như sau
    • Tối đa 30k file handle đang mở
    • Tối đa 30k khóa được giữ trong bộ nhớ
  • Khi cơ sở dữ liệu bị đẩy khỏi cache thì file handle sẽ được đóng
  • Các commit liên quan bao gồm actor store in lru cache, fix open handles

Ba DB SQLite cho trạng thái dịch vụ

  • Ngoài DB theo từng người dùng, còn có thêm 3 cơ sở dữ liệu SQLite riêng để quản lý trạng thái dịch vụ
    • service DB: quản lý thông tin tài khoản, mã mời, refresh token, v.v.
    • did cache DB: chỉ gồm một bảng duy nhất để cache DID resolution
    • sequencer DB: chỉ gồm một bảng duy nhất để quản lý thứ tự cập nhật repo của toàn bộ một dịch vụ
  • Mỗi tệp SQLite chạy ở WAL mode
  • Mục đích của WAL mode là cho phép đọc đồng thời và sao chép streaming
  • Bản phân phối PDS dự kiến sẽ bao gồm Litestream hoặc công cụ tương tự

Trạng thái review và hợp nhất

  • PR này gồm tổng cộng 143 commits và được hợp nhất từ nhánh pds-sqlite-refactor vào nhánh pds-v2
  • Ngày hợp nhất là 1 tháng 11, 2023, và merge commit là 8449ceb
  • Reviewer devinivy đã để lại nhiều ghi chú và bình luận trước khi phê duyệt thay đổi
  • devinivy đánh giá rằng bản refactor có “nhiều sự đơn giản hóa tuyệt vời” và nhìn chung cho cảm giác gọn gàng hơn
  • Sau khi hợp nhất, nhánh pds-sqlite-refactor đã bị xóa

Câu hỏi sau đó

  • Vào ngày 28 tháng 2, 2025, npetrangelo xem xét quy mô thay đổi của PR này và yêu cầu tóm tắt các trade-off giữa kiến trúc Postgres trước đây và kiến trúc SQLite được đưa vào bởi PR này
  • Nội dung được cung cấp không bao gồm câu trả lời từ phía Bluesky cho câu hỏi đó

1 bình luận

 
GN⁺ 2023-11-08
Ý kiến trên Hacker News
  • Tôi thích SQLite, nhưng cách tách riêng schema hoặc database cho từng tenant nhìn chung có rất nhiều khó khăn
    Nếu dùng bảo mật cấp hàng (RLS) trên một instance dùng chung, dù migration thất bại vẫn có thể rollback toàn bộ; nhưng với schema theo từng tenant, nếu migration dữ liệu thất bại vì dữ liệu không lường trước, người dùng sẽ bị kẹt ở các phiên bản schema khác nhau cho đến khi tìm ra nguyên nhân
    Khi đạt đến quy mô sharding thì dù sao chuyện tương tự cũng có thể xảy ra, nhưng trước thời điểm đó thì một database duy nhất là dễ nhất, và sau này cũng có thể cần hợp nhất dữ liệu hoặc chuyển quyền sở hữu tài nguyên một cách nguyên tử
    Tôi không phản đối cấu hình này, nó vẫn có chỗ dùng, nhưng ở công ty chúng tôi đang cố hết sức rời khỏi schema theo từng tenant. Nếu không đầu tư đúng mức thì có quá nhiều vấn đề, và tôi nghĩ hiếm khi người ta đã sẵn sàng cho điều đó ngay lúc nảy ra ý tưởng ban đầu
    Điều thú vị là khoảng 10 năm trước ứng dụng bắt đầu với SQLite theo từng tenant, rồi chuyển sang schema theo từng tenant trên PostgreSQL, và giờ đang chuyển sang một schema duy nhất có RLS, tức là đi theo hướng hoàn toàn ngược lại

    • Với tư cách người từng xử lý database khổng lồ trong production, tôi không muốn làm lại lần nữa
      Khi tải đủ lớn, mọi thay đổi đều trở nên rủi ro, vì không thể kiểm thử trọn vẹn mọi trường hợp cực đoan về hiệu năng
      Người dùng gói miễn phí tìm ra một đường code không có index rồi làm hỏng production cũng là một mẫu rất thường gặp

    • Việc một số người dùng bị kẹt ở phiên bản schema khác do migration dữ liệu thất bại có thể không phải vấn đề lớn
      Nếu dịch vụ lớn và phức tạp đến mức đó, thường schema upgrade sẽ được làm theo giai đoạn: 1. làm cho code tương thích với schema tương lai, 2. migrate dữ liệu, 3. gỡ hỗ trợ schema cũ
      Vì vậy thông thường hệ thống phải an toàn khi chạy lâu trong trạng thái giữa bước 1 và bước 2. Tất nhiên bug mới là ngoại lệ, nhưng từ góc độ vận hành, miễn là dùng quy trình này thì tôi vẫn thấy ổn với một hệ thống có thể quay về trạng thái trung gian của migration

    • Nếu sản phẩm có dưới 100 khách hàng, việc mỗi người dùng ở một phiên bản schema khác nhau có khi lại tốt
      Mỗi khách hàng có thể có lịch và yêu cầu nâng cấp khác nhau, và tôi cũng biết những doanh nghiệp làm tuỳ chỉnh cho một số khách hàng đến mức về cơ bản không còn chạy cùng một code nữa
      Rốt cuộc còn tuỳ vào cấu trúc kinh doanh

    • Công bằng mà nói, 10 năm trước RLS vẫn chưa có. Nó xuất hiện trong PostgreSQL 9.5 vào năm 2016

    • https://blog.turso.tech/introducing-embedded-replicas-deploy...

      https://electric-sql.com/

  • Tôi không hiểu câu “SQLite không hỗ trợ transaction đồng thời” nghĩa là gì
    Theo tôi biết thì nó có hỗ trợ, miễn là không truy cập file .db qua chia sẻ file như UNC hay NFS: https://www.sqlite.org/wal.html
    Tôi đã dùng nó để nhiều thread/process trên cùng một máy đọc và cập nhật database; nếu cần một view nhất quán hoặc không muốn giữ transaction quá lâu thì cũng có thể tạo snapshot bằng sqlite backup API
    Có thể tôi đã bỏ sót điều gì đó, và cũng đã vài năm tôi chưa đụng đến SQLite nên không chắc

    • Không phải vậy. Tôi đã nhầm. Thực tế gần với nhiều reader, một writer hơn
      Có lẽ lâu nay tôi cứ giả định như vậy và không kiểm tra đủ kỹ. Dù vậy, phần lớn database tôi tạo bằng SQLite thiên về đọc hơn là ghi
      Xin đính chính

    • Nếu chờ thêm, hctree [1] sẽ ổn định, và ta sẽ có thể chọn giữa cơ chế backend truyền thống và backend mới được triển khai có hỗ trợ concurrency

      [1] https://sqlite.org/hctree/doc/hctree/doc/hctree/index.html

    • Theo tài liệu, writer chỉ nối thêm nội dung mới vào cuối file WAL nên đọc và ghi có thể diễn ra đồng thời, nhưng vì chỉ có một file WAL nên chỉ có một writer có thể ghi cùng lúc
      Có vẻ ý bài gốc là các thao tác cập nhật phải được thực thi tuần tự

    • Nếu lưu lượng thấp thì vẫn hoạt động, nhưng khi transaction lớn hơn hoặc số lượt ghi đồng thời tăng lên, dù bật WAL thì đến một lúc nào đó cũng sẽ gặp lỗi database locked
      Có thể né phần nào ở tầng ứng dụng, nhưng nhìn chung nếu đã chạm đến điểm đó thì nên nghiêm túc cân nhắc một backend database khác

    • Ít nhất theo lần cuối tôi kiểm tra, có khả năng ý là không có khóa cấp hàng, và khóa cấp bảng cũng rất hạn chế
      Theo tài liệu, writer vẫn giữ khóa trên toàn bộ database

  • Thú vị, tôi thích chiến lược 1:1 giữa 1 người dùng và 1 database
    Tuy nhiên tôi tò mò họ xử lý dữ liệu cần tổng hợp giữa các người dùng như thế nào. Nếu tôi đang subscribe một người khác và người đó đăng bài, database của tôi được cập nhật bài mới ra sao, hay đây là kiến trúc chỉ áp dụng cho dữ liệu bền vững như dữ liệu profile hay quan hệ follow, còn dữ liệu tương tác như feed thì được xử lý riêng?
    Tôi cũng thích việc “connection pooling” chỉ đơn giản là giới hạn số handle đang mở bằng LRU cache. Việc mỗi kết nối DB là single-threaded nên xử lý concurrency ở cấp tenancy thay vì cấp connection cũng rất thú vị
    Có vẻ cũng dễ đặt rate limit theo từng database lên trên kiến trúc này để ngăn lạm dụng từ một người dùng cụ thể
    Tôi cũng tò mò liệu có cách đơn giản nào để cấu hình Litestream cho số lượng database tuỳ ý hay không

  • Luôn vui khi thấy việc dùng SQLite/Litestream trên server ngày càng tăng. Chúng tôi cũng đang dùng nó khi xây ứng dụng mới
    SQLite + Litestream là lựa chọn tốt hơn cho database theo tenant, và chi phí replicate/backup lên S3/R2 rẻ hơn rất nhiều so với database quản lý trên cloud đắt đỏ [1]
    Rẻ hơn tới 3900% so với SQLServer trên Azure

[1] https://docs.servicestack.net/ormlite/litestream

  • Không hiểu rẻ hơn 3900% nghĩa là gì

  • Ở công ty fintech trước đây, công ty lưu tài khoản khách hàng dưới dạng tệp sqlite3 được mã hóa trong blob storage, và nó khá phù hợp với mẫu truy cập

    • Tò mò không biết họ xử lý khóa tệp thế nào khi chỉnh sửa xong rồi tải lên lại
  • Nhìn bề ngoài thì trông như một sự kết hợp giữa tệ nhất và kinh khủng
    Mong ai đó viết một bài hay giải thích ưu điểm bằng số liệu thực tế và phân tích các khiếm khuyết dự kiến. Nếu học cho đến nơi đến chốn thì đây có thể là một chủ đề thật sự thú vị

    • Bạn có thể giải thích vì sao nó trông như “sự kết hợp giữa tệ nhất và kinh khủng” không?
      Nhìn bề ngoài, đặc biệt nếu giả định đang xây dựng một hệ thống phân tán để nhiều người dùng không phải quản trị viên hệ thống chuyên nghiệp chạy và triển khai, thì đây có vẻ là một lựa chọn khá hợp lý
      Mục tiêu ở đây có lẽ cũng nên như vậy, và tôi kỳ vọng mục tiêu thiết kế là tránh nhu cầu phải thiết lập, cấu hình và quản lý thêm cơ sở dữ liệu hay máy chủ khác
  • Mong ai đó hiểu rõ Bluesky hơn giải thích dữ liệu nào được lưu trong SQLite và dữ liệu nào thì không
    Tôi đang giả định những thứ như tin nhắn giữa người dùng với nhau thì không

    • Tôi nghĩ tin nhắn giữa người dùng với nhau cũng được lưu trong các cơ sở dữ liệu SQLite đó
      Hãy hình dung như email. Nếu bạn gửi một email và CC cho năm người, bảy người sẽ mỗi người lưu một bản sao của cùng email đó trên máy chủ email riêng của mình
      Tức là không có cấu trúc kiểu một cơ sở dữ liệu trung tâm chứa một email để những người khác tham chiếu đến
      Sharding cơ sở dữ liệu quan hệ về cơ bản cũng hoạt động theo kiểu này
      Việc phi chuẩn hóa dữ liệu như vậy gần như là bắt buộc khi ứng dụng mở rộng, đặc biệt trong các ứng dụng nhiều-nhiều có tỷ lệ đọc cao so với ghi
      Nếu tỷ lệ đọc so với ghi thấp, cấu trúc cơ sở dữ liệu quan hệ một master và nhiều slave cũng có thể xử lý lượng yêu cầu và dữ liệu lớn đến đáng ngạc nhiên
    • Nó chứa tất cả bài đăng và phản hồi mà bạn đăng với tư cách người dùng
      Hiện tại Bluesky thực tế đang tự host PDS gần như duy nhất, nhưng mục tiêu cuối cùng là mọi người dùng cuối đều có PDS của riêng mình
      Inrupt/SOLID gọi khái niệm này là “pod”
      Trên thực tế, hôm qua họ đã onboarding PDS production thứ hai, nên cũng có tiến triển
    • Nếu “tin nhắn” nghĩa là tin nhắn trực tiếp, tức tin nhắn riêng tư giữa hai bên, thì hiện Bluesky không có tính năng đó
      Chỉ có các tin nhắn công khai được phát sóng ra toàn thế giới
      Tôi chưa tìm hiểu riêng xem có kế hoạch cho tin nhắn trực tiếp hay không
  • Vì sao họ băm người dùng bằng sha256 rồi chia vào thư mục đích hai ký tự?
    md5 nhanh hơn nhiều và chẳng phải giải quyết cùng một vấn đề sao?

    • Tôi đoán là phép băm đó được thực hiện tương đối ít, nên khác biệt hiệu năng bị chìm trong nhiễu
      Giá trị lớn hơn là không phải trả lời câu hỏi “vì sao lại dùng hàm băm không an toàn”, đồng thời loại bỏ hoặc ít nhất giảm thiểu khả năng xảy ra một lớp vấn đề bảo mật
    • Ở quy mô đó, họ cũng có thể lo về va chạm
      Hoặc cũng có thể giống tôi, họ bị các công cụ bảo mật của công ty bủa vây nên không muốn phải tạo ngoại lệ riêng cho mỗi lần dùng md5
    • Để một hàm băm mật mã đã bị phá vẫn được dùng lung tung là điều không lành mạnh
      Nếu không cần hàm băm bảo mật thì có rất nhiều hàm băm phi mật mã nhanh
    • Khả năng cao đây là do giới hạn của hệ thống tệp, tức số lượng tệp tối đa trong một thư mục, hơn là do va chạm
  • Bluesky vẫn còn theo chế độ mời à?

    • Đúng, nhưng không phải vì lý do kiểu “growth hacking”
      Đó là cách hạn chế tăng trưởng trong khi họ mở rộng hệ thống ở phía backend và chống lạm dụng
      Có một hàng đợi riêng cho nhà phát triển, và bạn có thể nhận quyền truy cập khá nhanh: https://atproto.com/blog/call-for-developers