1 điểm bởi GN⁺ 2024-08-11 | 1 bình luận | Chia sẻ qua WhatsApp
  • Screenshotbot sử dụng trạng thái RAM của tiến trình web server làm kho lưu trữ dữ liệu mà không cần DB riêng, và khôi phục trạng thái sau sự cố bằng snapshot cùng transaction log
  • Giảm việc tuần tự hóa SQL, các lượt đi-về DB và dịch vụ tác vụ nền riêng biệt để đơn giản hóa trải nghiệm phát triển với một tiến trình duy nhất, đồng thời tận dụng index trong bộ nhớ và xử lý dựa trên thread
  • Tính sẵn sàng cao được đảm bảo bằng thuật toán đồng thuận Raft, sao chép transaction log sang 3 server; khi leader gặp sự cố, leader mới tiếp quản yêu cầu trong vài giây
  • Triển khai thực tế kết hợp Common Lisp, bknr.datastore, bknr.cluster mã nguồn mở, Baidu Braft và EFS; server thường chỉ khởi động lại khoảng 1–2 tháng một lần
  • Dù CI của khách hàng enterprise nổi tiếng gọi API hàng trăm lần cho mỗi commit và Pull Request, mức sử dụng CPU trên máy 4 core 16GB tối đa chỉ khoảng 20%, phần lớn đến từ xử lý ảnh

Kiến trúc xử lý trạng thái RAM như cơ sở dữ liệu

  • Các dịch vụ web truyền thống thường chọn một web framework như Rails, Django, Node cùng với một cơ sở dữ liệu như MySQL, PostgreSQL, MongoDB
  • Trong 10 năm qua, đã có những thay đổi về môi trường khiến cách tiếp cận này đáng được xem xét lại
    • Ổ đĩa trở nên nhanh hơn nhờ NVMe
    • Các ổ đĩa như EBS/EFS trở nên bền vững hơn
    • RAM rẻ hơn, khiến hầu hết dữ liệu của startup có thể nằm gọn trong RAM
    • Có thể thuê máy với hàng trăm core
    • Năm 2014, thuật toán đồng thuận Raft được công bố và số lượng triển khai vững chắc tăng lên
  • Kiến trúc này gộp dịch vụ web và instance cơ sở dữ liệu lại, sử dụng trạng thái bộ nhớ của tiến trình như một cơ sở dữ liệu
  • Không tuần tự hóa dữ liệu để lưu như SQLite, mà trực tiếp xử lý các object và field trong RAM như trạng thái ứng dụng

Explore: Cách đơn giản hóa vòng lặp sản phẩm ban đầu

  • Nếu mọi dữ liệu nằm trong RAM, không cần tuần tự hóa thành SQL query, và cũng giảm kiến trúc nhiều frontend server cùng kết nối vào một DB duy nhất
  • Khi tải tăng, có thể ứng phó bằng cách dùng server lớn hơn với nhiều RAM và CPU hơn
  • Index có thể dùng các cấu trúc như hash table trong bộ nhớ thay vì B-tree được tối ưu cho độ trễ đĩa
    • Với Screenshotbot, index dùng functional collections đóng vai trò quan trọng đối với khả năng mở rộng
  • Vì truy vấn dữ liệu là đọc từ RAM, không cần các cấu trúc đặc biệt hay Async-IO để giảm lượt đi-về DB
  • Tác vụ nền trở thành các thread chạy trong cùng một tiến trình lớn, và xử lý đồng thời phần lớn có thể giải quyết bằng mutex và condition variable trong bộ nhớ

Khôi phục sự cố: snapshot và transaction log

  • Để phòng trường hợp tiến trình crash, toàn bộ trạng thái RAM được lưu định kỳ dưới dạng snapshot
  • Các thay đổi sau snapshot gần nhất được ghi trước xuống đĩa dưới dạng transaction log, rồi mới thay đổi trạng thái bộ nhớ
    • foo.setBar(2) ghi một transaction rằng field bar của foo đã đổi thành 2, rồi mới thay đổi field thực tế
    • new Foo() ghi một transaction rằng object Foo đã được tạo, rồi mới trả về object mới
  • Khi khởi động lại, hệ thống đọc snapshot trước, rồi phát lại transaction log để khôi phục trạng thái
  • Thay đổi index không cần đưa vào transaction log
    • Ví dụ, nếu field bar của Foo có index, setBar sẽ cập nhật index; việc cập nhật này cũng diễn ra lại trong quá trình load snapshot hoặc phát lại transaction

Dạng mã mà một tiến trình duy nhất cho phép

  • Vì mọi request được xử lý trong cùng một tiến trình, có thể lưu closure trong bộ nhớ server để dùng cho việc xử lý request trang
  • URL dạng https://screenshotbot.io/n/nnnnnnn của Screenshotbot được ánh xạ tới các closure nội bộ
  • Do closure giữ tham chiếu object, nhu cầu truyền object ID hoặc tuần tự hóa object qua mỗi lần chuyển trang được giảm xuống
  • Phạm vi debug, profiling và monitoring cũng thu hẹp còn một dịch vụ
    • Không còn các điểm quan sát DB riêng như MySQL slow query log
    • Nếu một dịch vụ sập thì site cũng sập, nhưng khi chỉ có một dịch vụ và một server, khả năng xảy ra lỗi cũng được xem là thấp hơn
    • Nếu server chết, AWS có thể dựng server mới trong vài phút
  • Mã test cũng dễ hơn vì không cần mock cơ sở dữ liệu

Expand: Đảm bảo tính sẵn sàng cao bằng Raft

  • Nếu khách hàng rủi ro cao yêu cầu độ sẵn sàng 99,999%, kiến trúc một server là không đủ
    • Khi server gặp sự cố, có thể mất vài phút cho đến khi AWS dựng lại
    • Tiến trình cũng có thể mất vài phút để khôi phục snapshot từ đĩa
    • Trong quá trình redeploy, server có thể ngừng trong nhiều phút do dịch vụ khởi động lại
  • Thuật toán đồng thuận Raft sao chép transaction log của web server/cơ sở dữ liệu, vốn là một finite state machine
  • Khi sao chép sang 3 máy, nếu leader gặp sự cố, leader mới sẽ được bầu trong vài giây và tiếp tục xử lý request
  • Có thể biến một dịch vụ đơn giản thành cơ sở dữ liệu có tính sẵn sàng cao mà không làm thay đổi đáng kể cách developer viết code
  • Với cấu hình dựa trên Raft, cũng có thể rolling deploy mà không cần dừng server

Extract: Sharding và nút thắt dự kiến

  • Ở giai đoạn xử lý nhiều khách hàng lớn hơn, có thể áp dụng sharding như cách các doanh nghiệp lớn dùng với cơ sở dữ liệu
  • Mỗi shard có thể là một cluster riêng
  • Screenshotbot hiện đã cung cấp cluster riêng cho từng khách hàng enterprise
  • Meta đã chuyển sang Raft để xử lý sao chép cluster MySQL, còn Screenshotbot dùng cách tương tự mà không cần cơ sở dữ liệu riêng
  • Ứng viên nút thắt chính là khả năng mở rộng của commit-thread
    • Các thread đọc được song song hóa tốt
    • Một commit-thread áp dụng từng transaction một
    • Vì Raft commit nhiều transaction cùng lúc xuống đĩa, độ trễ đĩa không quan trọng
    • Điểm đáng lo là khi chi phí CPU của việc áp dụng transaction vượt quá hiệu năng của một core đơn lẻ
    • Trong trường hợp đó, có thể profile chi phí commit để chuyển một phần công việc ra ngoài transaction thread hoặc cân nhắc sharding

Stack thực tế của Screenshotbot

  • Screenshotbot sử dụng Common Lisp
  • Triển khai ban đầu dùng MySQL, nhưng do khó xử lý concurrency với MySQL nên đã thay bằng bknr.datastore
  • bknr.datastore là thư viện cung cấp cho Common Lisp kiến trúc đã mô tả ở giai đoạn Explore
  • Trong kiến trúc này, các thread trong một tiến trình duy nhất xử lý request web, nên multithreading mạnh là yếu tố quan trọng
    • Vì yêu cầu này, Ruby hoặc Python được xem là không phù hợp
  • Do duy trì closure trong bộ nhớ server, khó khởi động lại server thường xuyên
    • Khi khởi động lại sẽ mất closure
    • Thay vào đó, code được cập nhật bằng hot reloading trong tiến trình đang chạy
    • Common Lisp cung cấp các tính năng như reinitialize-instance, một chuẩn cập nhật object hiện có khi định nghĩa class thay đổi

Cluster, lưu trữ file và quy mô vận hành

  • Hiện server thường khởi động lại khoảng 1–2 tháng một lần
  • Khi cần khởi động lại, hệ thống thực hiện rolling restart trong cluster Raft
  • Mỗi bản cài đặt dùng cluster 3 server, cấu hình này cho phép một server bị down
  • Hiện không dùng Kubernetes và được xem là chưa cần thiết
  • Triển khai Raft là thư viện tự xây dựng trên bknr.datastore
    • bknr.cluster đã được phát hành mã nguồn mở
    • Nội bộ sử dụng Braft của Baidu
    • Braft xử lý snapshot nền, nhờ đó server vẫn tiếp tục xử lý request trong khi tạo snapshot
  • Các file ảnh hoặc blob không nên đưa vào datastore được lưu trên EFS mà ba server cùng chia sẻ
    • EFS là NFS có tính sẵn sàng cao
    • Được xem là dễ làm việc hơn S3 vì không cần xử lý riêng các điều kiện lỗi
    • Vì ghi xuống đĩa mà không tương tác với server bên ngoài, khả năng test cũng cao hơn

Hiệu năng hiện tại và phạm vi áp dụng

  • Screenshotbot hỗ trợ một số khách hàng enterprise lớn và một khách hàng đặc biệt nổi tiếng
  • Hệ thống chạy trong CI của khách hàng đó, nhận hàng trăm request API cho mỗi commit và Pull Request
  • Dù có tải này, xử lý request chỉ cần máy 4 core 16GB là đủ
    • Server sao chép cũng dùng máy tương tự và phần lớn ở trạng thái idle
    • Mức sử dụng CPU tối đa khoảng 20%
    • Phần lớn mức sử dụng CPU đến từ xử lý ảnh
  • Điều quan trọng là không thiết kế dựa trên quy mô lớn hơn quy mô cần thiết
  • Nếu chọn Common Lisp, cấu hình liên quan có thể dùng qua Screenshotbot OSS

1 bình luận

 
GN⁺ 2024-08-11
Ý kiến trên Hacker News
  • Kiến trúc này đại khái giống cách HashiCorp tạo ra Nomad, Consul và Vault (tôi là một trong những người bảo trì Nomad). Chắc chắn đây là một kiến trúc khác thường, nhưng khi đã quen thì trải nghiệm của lập trình viên khá tốt
    Trạng thái trong bộ nhớ có thể được giữ theo bất kỳ dạng nào bạn muốn, nên bạn có thể tự tạo các hàm lập chỉ mục và truy vấn phù hợp với ứng dụng. Có thể dùng SQLite :memory: cho Raft FSM, nhưng nếu có thể tự xây hoặc tìm được một kho lưu trữ giao dịch trong bộ nhớ (chúng tôi dùng go-memdb của riêng mình), việc đọc trạng thái chỉ còn là một lời gọi hàm. Việc ngăn đọc dữ liệu cũ hoặc thiên lệch về ghi cũng đơn giản. Mọi đối tượng được ghi đều có chỉ mục Raft, nên có thể tạo API kiểu “truy vấn đối tượng foo trên follower, nhưng đợi ít nhất đến chỉ mục 123”. Nó loại bỏ rất nhiều “ma thuật” mà thường ta đẩy vào RDBMS hoặc kho lưu trữ bên ngoài
    Tuy nhiên, tôi sẽ thận trọng khi chọn kiến trúc này cho một startup mới ngoài lĩnh vực “hạ tầng”. Vì về thực chất, bạn đang tạo ra cơ sở dữ liệu riêng. Bạn phải chọn kỹ hoặc tự viết các thành phần nền tảng như RPC giữa các node, tính bền vững trên đĩa, kho lưu trữ trạng thái giao dịch trong bộ nhớ. Nâng cấp đặc biệt khó, vì mã mới có thể cố ghi vào log Raft những entity mà node phiên bản cũ không hiểu. Tệ hơn nữa là cách xử lý thay đổi khiến node cũ hiểu sai. Không có bữa trưa miễn phí

    • Phần “có thể dùng SQLite :memory: cho Raft FSM” từng là thiết kế mặc định mà rqlite[1] dùng trong khoảng 7 năm đầu. Nhưng rqlite đã chuyển sang SQLite dựa trên đĩa, và khi dùng chế độ WAL cùng PRAGMA synchronous=OFF[2], nó nhanh ngang hoặc đủ gần với ghi vào RAM. Nhờ đó cũng tránh được các giới hạn của cơ sở dữ liệu SQLite :memory:, một trong số đó là giới hạn kích thước tối đa 2GB. Đáng lẽ tôi nên dùng chế độ đĩa ngay từ đầu, giờ mới nhận ra
      Vì rqlite dùng cùng thư viện Raft[3] như Nomad, có thể bạn đã biết một phần chuyện này
      Vấn đề nâng cấp thực sự tồn tại. Tôi tò mò không biết Nomad có hay gặp trong thực tế không. Trong 10 năm phát triển rqlite, việc đưa vào một kiểu Raft Entry mới là rất hiếm, và người dùng thực tế chỉ gặp một lần. Một cách xử lý là trước tiên phát hành một phiên bản hiểu kiểu mới nhưng tuyệt đối không ghi kiểu đó, rồi sau khi phiên bản ấy đã được triển khai hoàn toàn mới nâng lên phiên bản thực sự ghi kiểu mới. Tuy nhiên thực tế tôi chưa từng làm vậy, và nó cũng đòi hỏi kỷ luật từ người dùng cuối
      [1] https://www.rqlite.io
      [2] Nghe có vẻ nguy hiểm, nhưng trong thiết kế hiện tại của rqlite, khi khởi động nó tái dựng hoàn toàn cơ sở dữ liệu SQLite chính từ log Raft (log Raft được fsync ở mỗi lần ghi). Vì vậy ngay cả khi cơ sở dữ liệu SQLite bị hỏng do mất điện chẳng hạn, điều đó cũng không quá quan trọng vì cơ sở dữ liệu SQLite không phải kho dữ liệu có thẩm quyền của rqlite
      [3] https://github.com/hashicorp/raft
    • So với các phản hồi khác ở đây, tôi cũng cởi mở hơn với ý tưởng giữ dữ liệu trong bộ nhớ. Khi thấy trong bài viết nói họ dùng Common Lisp và hot reloading, tôi đã nghĩ: “Nếu là đội đó thì muốn làm gì cũng được. Chỉ là không phải ai cũng làm việc trong đội đó”
    • Câu “nâng cấp đặc biệt khó” thực sự đúng. Nhưng nó không áp dụng cho startup ở giai đoạn Explore chưa cần sao chép dữ liệu, và chúng tôi cũng đã làm như vậy trong thời gian dài. Đây chính là giai đoạn mà kiến trúc này hữu ích nhất cho việc lặp sản phẩm
      Tuy nhiên, khi bắt đầu dùng sao chép ở giai đoạn Expand thì đúng là sẽ phát sinh các bài toán kỹ thuật. Dù vậy, tất cả đều là những vấn đề có thể giải quyết. Trong Common Lisp có thể hot reload mã, nên một số migration trở nên dễ hơn nhiều, điều này cũng giúp ích
  • Vài chục năm trước, PG từng viết rằng ở Viaweb ông không dùng cơ sở dữ liệu, và thấy kỳ lạ khi ứng dụng web lại trở thành giống như frontend của cơ sở dữ liệu, trong khi ứng dụng desktop thì không như vậy[0]. HN cũng không dùng cơ sở dữ liệu
    Nhưng vì các ứng dụng desktop và mobile hiện đại thường dùng cơ sở dữ liệu, thường là SQLite, nên điều đó không còn hoàn toàn đúng nữa. Lý do là việc lưu trữ và truy vấn dữ liệu quan hệ đã cho thấy khá hữu ích trong một phạm vi ứng dụng rất rộng
    [0] https://www.paulgraham.com/vwfaq.html

    • Đọc liên kết thì có vẻ “cơ sở dữ liệu” không có cùng ý nghĩa với mọi người
      Trong vwfaq vẫn có nội dung nói rằng dữ liệu được đọc từ đĩa, và cũng có câu “khởi động một tiến trình để phản hồi yêu cầu HTTP”. Ở đây, “cơ sở dữ liệu” có vẻ chỉ một máy chủ riêng để duy trì dữ liệu bền vững, cùng kiến trúc phải giao tiếp với một máy chủ khác để lấy dữ liệu đó
      Theo định nghĩa này thì tất nhiên SQLite bị loại khỏi khái niệm cơ sở dữ liệu. Ngoài ra, nếu bạn đã đọc dữ liệu từ đĩa thì hoặc là bạn đang dùng cơ sở dữ liệu, hoặc là đã tự triển khai một lớp lưu trữ bền vững tạm bợ. Vấn đề là nếu khi khởi động ứng dụng bạn đọc dữ liệu từ SQLite thì có còn được xem là đang dùng cơ sở dữ liệu hay không
      Vấn đề của cách nghĩ này là nó bỏ qua sự thật rằng cốt lõi của cơ sở dữ liệu là lưu và lấy dữ liệu theo cách thuận tiện mà không phải bận tâm đến các chi tiết cấp thấp. Lưu dữ liệu vào cơ sở dữ liệu không có nghĩa là phải dựng một instance Postgres ở đâu đó rồi lấy dữ liệu qua web. Nếu bạn giữ toàn bộ dữ liệu trong bộ nhớ và có một tiến trình lưu snapshot ra đĩa bằng cấu trúc dữ liệu dạng log-structured, thì xin chúc mừng: bạn vừa tạo ra cơ sở dữ liệu riêng của mình
    • Rõ ràng là bài viết của PG có ảnh hưởng. Chúng tôi cũng dùng Common Lisp, và trong lĩnh vực này khó mà tránh được PG. Tuy nhiên có vẻ Viaweb không dùng transaction log như bknr.datastore, và nhờ đó quá trình phát triển trơn tru hơn nhiều
    • Khi PG viết Viaweb, bản thân SQLite cũng chưa phổ biến như bây giờ. Nói cho đúng hơn, khi đó SQLite còn chưa tồn tại. Nếu không có SQLite và lựa chọn thực tế gần như chỉ là kho khóa-giá trị, thì trong đa số trường hợp dùng filesystem cũng đã ổn
      Thứ hai, trong 20 năm qua, truy vấn RDBMS đã đơn giản hơn rất nhiều. Đã xuất hiện đủ loại ORM và row mapper giúp giảm mã lặp
      Các tính năng nâng cao như tìm kiếm toàn văn bản, hữu ích cho ứng dụng desktop và mobile, cũng đã xuất hiện. Ngày nay dùng RDBMS cho ứng dụng desktop là một lựa chọn tốt
    • HN không dùng cơ sở dữ liệu ư? Có thể giải thích thêm được không? Khá bất ngờ
    • Thời đó khác bây giờ. Theo tôi biết, Viaweb gồm nhiều instance Common Lisp, và toàn bộ trạng thái phiên người dùng nằm trong bộ nhớ của từng máy riêng lẻ. Tôi nhớ đã đọc ở đâu đó rằng họ vá lỗi trực tiếp trong production khi đang nói chuyện với người dùng qua điện thoại
      Web đã lớn hơn rất nhiều, và ngày nay nhiều thực hành kiểu này sẽ không còn hiệu quả. Nếu vừa nói chuyện với khách hàng vừa đẩy bản sửa trực tiếp lên máy production với gánh nặng kiểm thử ở mức hiện nay, hẳn nhiều người ở đây sẽ nghi ngờ tình trạng tâm thần của tôi
  • Tôi hiểu mong muốn thử nghiệm điều thú vị, nhưng làm thế này chỉ để không phải học những phần cơ bản nhất của MySQL hay Postgres thì có vẻ là một sự lãng phí thời gian khổng lồ. Chỉ cần xây trên đó là xong, đặc biệt nếu chạy trên public cloud
    Tôi không bị thuyết phục bởi các lập luận về độ trễ round-trip tăng lên hay vấn đề đồng thời. Vấn đề sau có các cách giải quyết đơn giản như tuning cơ bản hoặc tách riêng những khách hàng gây ồn ào. Trong một bài khác trên blog của họ, họ nói về khả năng thêm 10 triệu hàng mỗi ngày và các thách thức indexing, nhưng điều đó đúng nghĩa là chẳng là gì cả. Ngay cả khi gấp 10 lần con số đó, tôi cũng không nghĩ đó là lý do chính đáng để phải engineering một giải pháp tùy biến
    Cho đến khi thật sự cần, “worse is better” vẫn đúng; và khi thời điểm đó đến, bạn sẽ biết chắc. Lúc ấy bạn cũng sẽ biết nút thắt nằm ở đâu, nên có thể phản ứng khôn ngoan hơn thay vì over-engineer ngay từ đầu

    • Với engine cơ sở dữ liệu đặt trên server, vẫn có thể đưa ra lập luận rằng giảm được các cuộc gọi mạng. Dù đáng nghi, nhưng vẫn có thể
      Nhưng việc blogger cho rằng lý do không chọn SQLite là vì nó có thể có các tính năng không cần thiết thì thật khó hiểu. Điều đó vô lý và không biện minh được gì
      Bài viết đọc như bắt đầu từ một giải pháp tệ cho một vấn đề giả định, rồi tuyệt vọng kéo ra các lập luận gượng ép để bác bỏ giải pháp hiển nhiên
    • Điều tôi tò mò là nếu họ không dành thời gian phát minh lại bánh xe thì liệu doanh nghiệp có thành công không. Chỉ riêng việc làm công khai và viết blog về nó cũng đã giúp quảng bá sản phẩm và thể hiện năng lực kỹ thuật. Nếu họ dùng các công nghệ nhàm chán, ghép lại là chạy tốt, thì có lẽ sẽ ít chuyện để kể hơn, và vì thế hiệu ứng PR cũng kém hơn chăng?
      Tôi tự hỏi liệu suy nghĩ của mình có sai không, hay nỗ lực bổ sung gây tranh cãi nhưng có vẻ không cần thiết này thực ra là một phần của sản phẩm và là cách để thành công trong không gian này
  • Mở đầu bằng câu “không phải tôi đang nói đến thứ như SQLite, nơi dữ liệu vẫn bị tuần tự hóa”, nhưng rốt cuộc lại kết thúc bằng một transaction log tự chế vẫn cần tuần tự hóa và cũng phải sao chép, nghe khá lạ. Việc nhân bản cơ sở dữ liệu vốn cũng làm như vậy
    Nếu toàn bộ tải dồn vào một máy chủ, thì cứ chạy cơ sở dữ liệu trên máy chủ đó và quên đi những thứ kiểu “kiến trúc đặc biệt để giảm lượt đi-về đến cơ sở dữ liệu” là được. Nếu toàn bộ dữ liệu nằm vừa trong RAM, bạn có thể dùng ramdisk cho cơ sở dữ liệu nếu muốn, rồi sao chép sang lưu trữ bền vững bằng công cụ tiêu chuẩn. Như thế mới thực sự đơn giản

    • Nhìn chung có cảm giác có thể tóm tắt là “tự làm SQLite + nhân bản Raft”. Chỉ có điều thiếu đi độ tin cậy đã được kiểm chứng của SQLite, cũng như khả năng chuyển dữ liệu từ bộ nhớ xuống đĩa một cách hiệu quả
      Vì vậy về cơ bản nó giống https://litestream.io/. Nhờ cấu hình Raft rõ ràng, việc chuyển đổi có thể nhanh hơn. Tôi không dùng Litestream nên không biết các khác biệt tinh tế, nhưng nghe rất giống
      Bỏ qua sự đơn giản hóa quá mức này, bản thân ý tưởng thì tôi khá thích, và bài viết cũng thuyết phục khá tốt về mặt khái niệm. Với nhiều hệ thống, ngay cả khi thành công bất thường, nó vẫn sẽ mở rộng đủ để gánh phần lớn hoặc toàn bộ hoạt động kinh doanh, và hiệu năng sẽ tốt đến mức vô lý so với gần như mọi phương án thay thế
    • Tôi nghĩ ramdisk cũng không cần thiết. Cơ sở dữ liệu vốn đã cache mọi thứ trong bộ nhớ, chỉ các lần ghi mới chạm tới đĩa
      Cứ cold start cơ sở dữ liệu rồi chạy một câu select khá lớn hai lần là thấy
    • Điều quan trọng là hiểu rằng mọi startup đều đi qua ba giai đoạn Explore, Expand, Extract. Thứ đơn giản ở một giai đoạn không nhất thiết đơn giản ở giai đoạn khác
      Cơ sở dữ liệu giao dịch đơn giản ở giai đoạn Expand và Extract, nhưng ở giai đoạn Explore nó lại tạo thêm gánh nặng vì khiến bạn tập trung vào vấn đề hạ tầng thay vì sản phẩm. Ở giai đoạn Explore chưa có khách hàng, nên cũng chưa có dữ liệu, và độ tin cậy của dữ liệu cũng không quan trọng
      Cách giữ mọi thứ trong bộ nhớ với bknr.datastore (không sao chép) là đơn giản ở giai đoạn Explore. Nhưng khi bước vào giai đoạn Expand, nó phát sinh gánh nặng vận hành để bảo đảm tính nhất quán dữ liệu
      Tuy vậy, đến lúc đạt tới giai đoạn Expand thì bạn đã kiểm chứng được sản phẩm và đã viết khá nhiều code. Viết lại toàn bộ bằng cơ sở dữ liệu giao dịch là không hợp lý; thêm nhân bản Raft lên trên sẽ dễ hơn
    • Đồng ý. Tự làm lại WAL nghĩa là cũng tự làm lại, hoặc bỏ qua, tất cả những rắc rối đi kèm. Có vẻ việc khôi phục từ log mất khá lâu, nên có lẽ họ thậm chí còn chưa thật sự xử lý ổn checkpoint log
    • Các hệ thống giao dịch tài chính thì thẳng thắn đặt mọi thứ trong các cấu trúc được cấp phát sẵn trong RAM. Vấn đề là bạn chọn đánh đổi nào
  • Lập luận của bài này khiến tôi bối rối. Đây là cách làm cho ứng dụng có trạng thái đơn giản hơn và nhanh hơn à?
    Tiền đề thì yếu, còn tuyên bố thì quá đà. Tác giả phóng đại độ khó của tuần tự hóa để khiến một lập luận yếu trông mạnh hơn

    • Bài blog để lại cảm giác rất rõ kiểu “hãy xem chúng tôi thông minh đến đâu”
      Những người như vậy thường khó làm việc cùng. Thật mừng là họ đã tìm được một startup để đánh chìm, để tôi khỏi phải đối phó
    • Rồi sau đó họ lại triển khai tuần tự hóa để ghi giao dịch vào log và nhân bản sang các node khác
  • Khi bắt đầu một dự án mới, cấu trúc dữ liệu thường là “danh sách các mục có thuộc tính”. Ví dụ hiện tôi đang viết một ứng dụng fitness, dữ liệu là danh sách bài tập; mỗi bài tập có tiêu đề, mô tả, URL video và các thuộc tính khác
    Thường thì tôi bắt đầu bằng cách đặt các mục đó vào các file YAML trong thư mục data. Thực tế là một phương ngữ YAML tùy chỉnh, loại bỏ những điểm kỳ quặc của YAML gốc. Mỗi giá trị là chuỗi, không có chuyển đổi kiểu “ma thuật”. Tạo mục mới chỉ là vim crunches.yaml rồi nhập dữ liệu; chỉnh sửa và xóa trong cấu trúc dữ liệu này cũng rất dễ
    Khi dự án lớn hơn, thường tôi sẽ tạo schema DB và chuyển các mục sang MariaDB hoặc SQLite
    Lần này tôi định chuyển các mục (bài tập) sang cột JSON trong DB SQLite. Lưu mọi thuộc tính của một mục vào một trường JSON duy nhất, rồi viết một trình duyệt DB nhỏ cho phép chỉnh sửa trường JSON như YAML. Mục tiêu là giữ sự tiện lợi khi chỉnh sửa dữ liệu con người đọc được
    Việc viết trình duyệt DB có vẻ khá thẳng tiến. Dùng một chút ncurses để duyệt bảng, chọn một bảng, duyệt các hàng, chèn/xóa hàng. Khi chỉnh sửa trường thì mở Vim. Nếu trường là JSON, chuyển sang YAML trước khi đưa vào Vim; khi người dùng thoát Vim thì chuyển ngược lại thành JSON

  • Phần đầu bài viết mô tả về cơ bản khá giống cách các máy NUMA từng hoạt động (ví dụ SGI Altix hoặc UV). Ngoài ra, lợi thế họ nêu là độ trễ thấp và khả năng song song hóa công việc bằng multithreading trên RAM lớn. Clustering xuất hiện như một phương án chi phí thấp thay cho những cỗ máy hơn 1 triệu USD. Nó cũng có điểm tương đồng với tính bền vững của AS/400, nơi ứng dụng chỉ cần ghi vào bộ nhớ và dữ liệu được ánh xạ xuống đĩa một cách trong suốt
    Giờ thì chúng ta như đang quay ngược thời gian để lấy lại lợi ích của các máy NUMA dạng cluster bằng phần cứng rẻ tiền. Trong quá trình đó cũng có những cải tiến, và bài viết đọc khá thú vị
    Một kỹ thuật khác trước đây là loại bỏ TCP/IP stack bên trong cluster để tránh các vấn đề liên quan. Những giải pháp như Active Messages là một lớp mỏng nằm trên phần cứng. Cũng có các thiết kế router mạng tích hợp tính nhất quán mạnh. Họ có thể làm được khá nhiều việc
    Khi lớn lên, cũng có cơ hội về phần cứng. Ở phía CPU, SGI đã làm hai việc. Máy NUMA mở rộng số CPU và RAM trong một hệ thống, và cắm FPGA trực tiếp vào bus bộ nhớ để dùng làm bộ tăng tốc tùy chỉnh. Cuối cùng, một số bài báo khoa học máy tính đã sửa tập lệnh của bộ xử lý, mạng nội bộ trong chip, v.v. để loại bỏ hoặc giảm nút thắt multithreading. Những chip như OpenPiton tăng số lõi bằng các core mở và có thể tùy biến (ví dụ: 32 core)

  • “Hãy tưởng tượng ta có thể tạo ra những thứ tuyệt vời đến mức nào nếu không cần tuần tự hóa dữ liệu thành các truy vấn SQL” — điều đó đã tồn tại trong các triển khai mô hình actor[0] đủ trưởng thành, chẳng hạn Akka Event Sourcing[1]. Nó cũng xử lý vấn đề tiếp theo:
    “Nhưng phần quan trọng là làm sao khôi phục khi tiến trình bị crash. Câu trả lời rất dễ: định kỳ chụp snapshot toàn bộ RAM là được”
    Về bản chất, việc này đã được giải quyết mà không cần tạo ra một “kiến trúc mới cho phát triển web”. Cũng có các nỗ lực mã nguồn mở khám phá giao thức RAFT bằng actor ở đây[2] và đây[3]
    0 - https://en.wikipedia.org/wiki/History_of_the_Actor_model
    1 - https://doc.akka.io/docs/akka/current/typed/persistence.html
    2 - https://github.com/Michael-Dratch/RAFT_Implementation
    3 - https://github.com/invkrh/akka-raft

  • Ý nghĩ đầu tiên của tôi là: “À, đây là cách mình từng làm khi viết bằng Common Lisp; thú vị là có người đã tái khám phá kỹ thuật đó”
    Nhưng hóa ra không phải vậy, chỉ là còn có thêm dân Lisp mà thôi

  • “RAM rất rẻ” có thể là hiểu lầm lớn nhất của giới lập trình viên
    SSD đã tốt hơn 100–10000 lần xét theo throughput hoặc IOPS, và hiệu năng trên mỗi đô la của vCPU cũng tăng 20–50 lần. Từ 45/32nm giờ đã xuống 5nm/3nm, và số lệnh xử lý trên mỗi chu kỳ xung nhịp cũng cao hơn nhiều
    Nhưng giá RAM gần như không giảm nhiều như CPU hay SSD. Nó có thể đã nhanh hơn nhiều, có thể cắm được nhiều bộ nhớ hơn nhờ chip mật độ cao hơn, và số kênh có thể đã tăng từ dual-channel lên 8 hoặc 12 kênh. Nhưng nếu nhìn giá spot DRAM giai đoạn 2008–2022, mức giá DRAM thấp nhất đã ba lần ở quanh cùng mức khoảng 2,8 USD/GB. Trong cùng giai đoạn, theo chu kỳ giá dao động lên xuống trong khoảng 6–8 USD/GB. Nói cách khác, nếu bạn mua DRAM ở đáy hoặc đỉnh trong khoảng 15 năm qua, thì bỏ qua lạm phát, giá sẽ vẫn tương tự nhau ở mức khoảng ±10–20%
    Chỉ đến giữa năm 2022, rào cản 2,8 USD/GB mới bị phá vỡ và giá rơi xuống gần 1 USD/GB, rồi ổn định quanh khoảng 2 USD/GB với DDR5
    Giờ thì bạn có thể lắp 4TB RAM vào một máy chủ. Nhưng điều đó không có nghĩa DRAM cực rẻ. Lập trình viên trung bình hoặc lập trình viên ở Big Tech giờ kiếm được nhiều hơn hẳn so với năm 2010, nên họ cảm thấy RAM dễ chi trả hơn nhiều. Thực tế là ngay cả ở mức đáy trong 15 năm qua, giá DRAM cùng lắm chỉ giảm hơn 2 lần một chút. Và nhiều khả năng giá DRAM sẽ lại tăng vọt trong 1–2 năm tới

    • Cũng có thể diễn giải theo cách khác. Dung lượng RAM tối đa của từng node đã tăng mạnh trong vài thập kỷ qua
      Lấy ví dụ đơn giản: nếu 20 năm trước một node nhất định bị giới hạn ở 16GB RAM, thì để có 4TB RAM trong hệ thống sẽ cần 256 node (chưa tính overhead của từng OS)
      Ngày nay, một node đơn lẻ có thể chứa toàn bộ 4TB đó trong một chassis
      Tổng chi phí của bản thân chip RAM có thể không thay đổi, nhưng chi phí để thực sự sử dụng lượng RAM đó trong một hệ thống vật lý đã giảm mạnh
    • Đồng thời, nhiều lập trình viên tìm đến hệ thống phân tán quá sớm ngay cả khi chỉ cần mua thêm RAM là được. Có lẽ ý của tác giả cũng là hướng đó
    • Cảm ơn vì dữ liệu. Tôi đồng ý với phản hồi khác. Có lẽ lẽ ra nên nói là việc kiếm được máy có hàng trăm GB RAM đã dễ hơn, thay vì nói “rẻ”