Bài viết này được viết bởi Colin Weld và Connor Adams. Đọc tại đây hoặc trên blog của chúng tôi.
Tại Modal, chúng tôi xây dựng các sandbox, cùng với những thứ khác. Các agent chạy trong sandbox, và agent đang "ăn" phần mềm. Ngày nay, Modal chạy hàng triệu sandbox mỗi ngày, hỗ trợ tới năm mươi nghìn sandbox đồng thời cho mỗi khách hàng, và hỗ trợ nhiều trường hợp sử dụng ở quy mô lớn, từ học tăng cường đến các agent nền.
Ngày càng nhiều, người dùng của chúng tôi yêu cầu nhiều sandbox hơn, được tạo với tốc độ cao hơn. Học tăng cường có thể yêu cầu chạy hàng triệu sandbox đồng thời và tạo ra các đợt bùng nổ hàng trăm nghìn sandbox khi bắt đầu triển khai. Tương tự, các agent ngày càng yêu cầu quy mô lớn và tốc độ tạo đồng thời cao để xử lý các đợt tăng đột biến lưu lượng.
Nền tảng sandbox hiện tại của chúng tôi thực sự tốt, nhưng nó không được thiết kế cho những quy mô này; cũng không có giải pháp hiện có nào khác. Chúng tôi đam mê quy mô và hiệu suất, và chúng tôi muốn cơ sở hạ tầng của mình thúc đẩy sự phát triển của các agent, chứ không phải thêm rào cản. Vì vậy, chúng tôi đã quay lại bảng vẽ.
Trong vài tháng qua, chúng tôi đã xây dựng lại nền tảng sandbox cốt lõi từ đầu, tập trung vào cả quy mô và độ tin cậy. Trên hệ thống mới của chúng tôi, người dùng có thể chạy hàng triệu sandbox đồng thời và tạo hàng chục nghìn sandbox mỗi giây. Chúng tôi đã loại bỏ tất cả các nút thắt cổ chai tập trung khỏi mặt phẳng điều khiển, do đó không có giới hạn mở rộng thực tế nào, và chúng tôi đã tối ưu hóa mọi phần của quá trình lập lịch và khởi động container, đơn giản hóa đường dẫn lập lịch thành một lớp các bộ cân bằng tải, tạo container trực tiếp trên cụm worker của chúng tôi.
Để chứng minh khả năng của nền tảng, chúng tôi đã chạy một triệu sandbox đồng thời, tạo tất cả 1 triệu trong vòng chưa đầy một phút.

Bằng chứng cho thấy chúng tôi có thể chạy rất nhiều sandbox.
Tại sao hầu hết các giải pháp không mở rộng được
Chạy 1 triệu sandbox đẩy các giới hạn của bất kỳ nền tảng container nào, không chỉ vì số lượng container khổng lồ, mà còn vì chạy nhiều sandbox như vậy đòi hỏi hàng chục nghìn nút tính toán. Sẽ có nhiều thao tác hoặc là O(containers), hoặc O(nodes), hoặc cả hai, điều này sẽ khiến các nền tảng container truyền thống chạm tới giới hạn mở rộng.
Đối với Kubernetes, ví dụ:
- thuật toán lập lịch là O(n x p) cho n nút và p pod trong trường hợp xấu nhất, và việc lập lịch được tuần tự hóa theo mặc định.
- mỗi pod gây ra nhiều lần ghi vào etcd (kho lưu trữ bền vững trung tâm của Kubernetes) trong suốt vòng đời của nó, điều này có thể tạo ra các vấn đề nghiêm trọng dưới tốc độ tạo pod cao hoặc tỷ lệ thay đổi pod cao, và etcd không thể phân mảnh gốc trong một keyspace.
- mỗi nút phải ghi vào etcd ít nhất một lần mỗi khoảng thời gian heartbeat để báo hiệu trạng thái sống, do đó tải ghi baseline của etcd là O(nodes) hoàn toàn độc lập với việc tạo pod.

Xấp xỉ luồng lập lịch của Kubernetes. Các pod mới được ghi vào etcd (một kho lưu trữ bền vững nhất quán mạnh) bởi API server. Bộ lập lịch Kubernetes theo dõi các pod chưa được gán mới, và gán chúng vào các nút thông qua một cuộc gọi tới API server, cuộc gọi này lại ghi vào etcd; sau khi quá trình ghi này hoàn tất, một nút có thể bắt đầu pod.
Kubernetes có thể được mở rộng, nhưng đòi hỏi công việc nghiêm túc. Để chạy số lượng lớn các nút, etcd thường phải được viết lại hoặc thay thế. Hỗ trợ thông lượng lập lịch cao đòi hỏi xây dựng một hệ thống scatter-gather phức tạp để song song hóa thuật toán lập lịch trong khi vẫn duy trì một nguồn sự thật duy nhất cho trạng thái pod. Việc phân mảnh và song song hóa không dễ dàng theo mặc định, bởi vì Kubernetes dựa trên tính nhất quán mạnh như một xương sống của thiết kế.
Kiến trúc sandbox ban đầu của Modal cũng có các vấn đề tương tự. Giống như Kubernetes, chúng tôi dựa trên tính nhất quán mạnh trong toàn bộ backend, do đó việc tạo và lập lịch sandbox đòi hỏi sự phối hợp toàn cầu, và O(sandbox) ghi vào Postgres, mà chúng tôi không thể phân mảnh một cách đơn giản.

Kiến trúc mặt phẳng điều khiển sandbox ban đầu của Modal. Khi sandbox được tạo, chúng được đặt vào một hàng đợi và ghi vào Postgres. Việc lập lịch là lạc quan và chạy song song, với sự phối hợp trung tâm cần thiết để tránh xung đột. Gán một sandbox cho một worker (nút tính toán) yêu cầu một lần ghi bổ sung vào Postgres.
Bởi vì chúng tôi không xây dựng trên Kubernetes, chúng tôi đã có thể mở rộng nhiều phần của hệ thống này. Ví dụ, việc lập lịch được song song hóa theo mặc định, cho phép chúng tôi đạt được tốc độ tạo sandbox bùng nổ rất cao. Nhưng khi chúng tôi mở rộng lên số lượng nút và sandbox ngày càng lớn, chúng tôi liên tục gặp phải các nút thắt cổ chai mới phát sinh từ các thao tác hoặc là O(sandbox) hoặc O(nodes) nhưng không dễ dàng mở rộng.
Ví dụ, chúng tôi chạy một workflow bền vững cho mỗi sandbox hoàn thành, do đó tỷ lệ thay đổi sandbox cao sẽ tạo ra các backlog sự kiện khổng lồ. Chúng tôi liên tục gặp các RPC được gọi với tốc độ O(sandbox) gây ra các vấn đề tải bất ngờ trong toàn bộ hệ thống. Và số lượng nút khổng lồ cần thiết để chạy số lượng lớn sandbox đã gây ra nhiều vấn đề hạ nguồn trong quản lý nút và tự động mở rộng quy mô. Cuối cùng, mặc dù chúng tôi có thể giải quyết, nhưng việc để lại một instance Postgres không phân mảnh trong đường dẫn quan trọng của tất cả việc tạo và lập lịch sandbox hóa ra là một ý tưởng tồi.
Mở khóa quy mô vô hạn
Chúng tôi nhanh chóng nhận ra rằng đạt được quy mô mong muốn đòi hỏi phải suy nghĩ lại kiến trúc từ đầu. Chúng tôi muốn chạy hàng triệu sandbox và tạo hàng chục nghìn sandbox mỗi giây, điều này đòi hỏi các đặc tính mở rộng tốt hơn nhiều so với bất kỳ thứ gì hiện có. Thay vì cố gắng phát triển những gì chúng tôi có, chúng tôi tin rằng con đường nhanh nhất và sạch nhất là bắt đầu lại.
Để tối ưu hóa cho quy mô, chúng tôi quyết định rằng mọi thứ có tải O(sandbox) hoặc O(nodes) phải có khả năng mở rộng theo chiều ngang theo mặc định, đường dẫn tạo sandbox phải đơn giản nhất có thể, và mọi thứ khác là thứ yếu. Giải pháp chúng tôi đưa ra khác biệt đáng kể so với các hệ thống hiện có. Chúng tôi hoàn toàn loại bỏ bất kỳ loại phối hợp trung tâm nào, và đánh đổi tính nhất quán toàn cầu để lấy khả năng mở rộng và hiệu suất ở mọi nơi trên đường dẫn quan trọng để chạy và tạo sandbox. Đây là cách nó hoạt động:
- Thay vì một bộ lập lịch tuần tự duy nhất, chúng tôi chạy một đội tàu các máy chủ lập lịch xử lý các yêu cầu tạo sandbox đồng thời. Để xử lý một yêu cầu tạo, một máy chủ lập lịch chạy một thuật toán lập lịch nhanh dựa trên dữ liệu được lưu trong bộ nhớ đệm. Kết quả là việc lập lịch mở rộng theo chiều ngang, và trông giống như cân bằng tải hơn là lập lịch container truyền thống.
- Thay vì một kho dữ liệu bền vững trung tâm đóng vai trò là nguồn sự thật cho trạng thái sandbox và worker, như cách hầu hết các nền tảng container hoạt động, mọi worker trong hệ thống mới của chúng tôi là nguồn sự thật của riêng nó. Các worker định kỳ công bố trạng thái của chúng vào một luồng Redis. Các máy chủ lập lịch tiêu thụ trạng thái này một cách không đồng bộ và sử dụng nó để đưa ra quyết định lập lịch. Khi một máy chủ lập lịch quyết định worker nào sẽ tạo sandbox, nó liên hệ trực tiếp với worker qua RPC để yêu cầu tạo sandbox. Các worker chấp nhận yêu cầu lập lịch nếu chúng có tài nguyên trống, nếu không thì từ chối.
- Chúng tôi không có kho dữ liệu nào trong đường dẫn quan trọng của việc tạo sandbox, điều này cải thiện khả năng mở rộng và độ tin cậy. Mặc dù chúng tôi cần ghi siêu dữ liệu và kết quả sandbox vào bộ nhớ bền vững, chúng tôi làm điều đó phần lớn một cách không đồng bộ.
- Ngoài việc tạo sandbox, chúng tôi không có RPC nào là O(sandbox). Các worker gộp các thông điệp điều khiển cho nhiều sandbox lại với nhau trong các RPC đơn lẻ, theo tinh thần của các ý tưởng từ thiết kế hướng dữ liệu.

Thiết kế cuối cùng của chúng tôi, lần đầu tiên chúng tôi vẽ nó trên bảng trắng.

Đường dẫn tạo sandbox trong kiến trúc sandbox v2 của Modal. Các yêu cầu tạo sandbox được xử lý bởi các máy chủ lập lịch mở rộng theo chiều ngang, sau đó chọn một worker bằng thuật toán cân bằng tải trong bộ nhớ nhanh, và liên hệ trực tiếp với worker (nút tính toán) để tạo sandbox. Các đối tượng sandbox được lưu trữ trong Redis, nhưng không nằm trong đường dẫn quan trọng.
Kết quả là đường dẫn tạo sandbox chỉ yêu cầu hai bước nhảy mạng và một thao tác CPU rẻ. Không có nút thắt cổ chai trung tâm hoặc chi phí phối hợp, không có điểm lỗi đơn lẻ, và do đó không có giới hạn thực tế nào đối với quy mô sandbox tổng hợp hoặc thông lượng tạo sandbox. Chúng tôi có thể thêm nhiều bộ lập lịch hoặc worker khi cần. Nút thắt cổ chai sắp xảy ra nhất là tất cả các worker đều công bố trạng thái vào một luồng Redis duy nhất, nhưng thử nghiệm tải đã gợi ý rằng điều này vẫn khả thi cho đến khi vượt quá 100.000 worker; và chúng tôi không phụ thuộc vào thứ tự trên luồng anyway, do đó sẽ dễ dàng chỉ cần thêm nhiều luồng hơn. Theo thiết kế, chúng tôi tránh được các vấn đề ngăn cản các giải pháp hiện có mở rộng quy mô.
Xây dựng giải pháp này không hề dễ dàng! Toàn bộ quá trình phát triển đã mất hàng tháng trời làm việc, trải dài trên hầu hết các hệ thống lớn trong backend của chúng tôi. Chúng tôi đã dành hàng giờ trên bảng trắng. Bốn người chúng tôi đã chuyển đến một ngôi nhà cho thuê ở Miami Beach để xây dựng một nguyên mẫu của hệ thống mới mà chúng tôi muốn, không bị phân tâm. Chúng tôi đã dành tám ngày viết mã cho đến khi không thể về mặt thể chất, chơi cờ nhanh để phục hồi, nhảy xuống biển, và sau đó quay lại ngay với mã, chiến đấu để hệ thống mới của chúng tôi sạch sẽ và hoạt động.

Kỹ sư giỏi nhất của chúng tôi thư giãn ở Miami Beach.
Khi chúng tôi đã làm cho các phần cốt lõi hoạt động (và quay lại New York), chúng tôi cũng cần triển khai lại mọi tính năng Sandbox và tất cả khả năng quan sát Sandbox trên hệ thống mới của mình. Dự án này cũng đòi hỏi những thay đổi đối với ngăn xếp quản lý worker cốt lõi của chúng tôi, cũng như runtime container của chúng tôi. Ví dụ, một vấn đề thú vị chúng tôi gặp phải là các bộ lập lịch sandbox mới của chúng tôi có thể đẩy các container đến worker nhanh đến mức nhiều container bắt đầu cùng lúc sẽ tranh giành khóa rtnl trong nhân Linux khi thiết lập các quy tắc mạng container và mất hàng chục giây để khởi động, do đó chúng tôi phải thay đổi thiết lập mạng container cho sandbox chỉ để worker của chúng tôi không bị nổ tung khi bị tràn ngập bởi các yêu cầu tạo sandbox.
Hiệu suất của chúng tôi như thế nào
Chúng tôi đã đo điểm chuẩn hệ thống của mình bằng cách khởi tạo 1 triệu sandbox nhanh nhất có thể. Ở cấp độ cao, chúng tôi có thể tạo một triệu sandbox trong vòng chưa đầy một phút, trong đó nút thắt cổ chai chính là bản thân điểm chuẩn. Thời gian từ khi tạo đến khi sandbox có thể tương tác vẫn duy trì ở mức thấp nhất quán, và chúng tôi không thấy sự suy giảm thực sự nào khi mở rộng quy mô.

Phân phối và eCDF của các yêu cầu tạo sandbox. Một yêu cầu tạo sandbox trả về khi các máy chủ lập lịch của chúng tôi đã gán thành công một sandbox cho một worker và nó đã bắt đầu khởi động.
Chúng tôi tin rằng điều này là như mong đợi, dựa trên thiết kế của chúng tôi. Không có sự phối hợp trong đường dẫn lập lịch, do đó việc lập lịch sẽ vẫn rất nhanh, độc lập với tính đồng thời và quy mô. Theo như chúng tôi quan tâm, chúng tôi không có giới hạn nghiêm trọng nào đối với việc lập lịch sandbox đồng thời hoặc quy mô ngoài dung lượng khả dụng, và việc quản lý dung lượng là điều chúng tôi đã làm tốt.

Biểu đồ phân tán của 10k thời gian bắt đầu sandbox từ bài kiểm tra 1M sandbox của chúng tôi, được chọn ngẫu nhiên từ 1 triệu sandbox.
Thời gian bắt đầu sandbox trên hệ thống mới của chúng tôi (độ trễ từ khi client lần đầu tiên cố gắng tạo sandbox, đến khi sandbox có thể chạy mã người dùng) dưới nửa giây ở mức trung vị, và vẫn ổn định ở quy mô lớn. Chúng cũng nhanh hơn đáng kể so với hệ thống cũ của chúng tôi, phần lớn là do việc lập lịch nhanh hơn nhiều — bây giờ chỉ mất hàng chục mili giây. Phần đuôi dài của độ trễ dài hơn một chút so với mong muốn của chúng tôi. Chúng tôi cho rằng phần lớn đuôi này là do sự tranh chấp nhân và mạng (bao gồm cả tranh chấp khóa rtnl đã đề cập trước đó) khi nhiều sandbox bắt đầu đồng thời trên cùng một worker, và chúng tôi đang làm việc để giảm nó. Ngoài ra, đuôi ở quy mô lớn là có thật. Chúng tôi hy vọng điều này sẽ cải thiện khi chúng tôi tối ưu hóa đường dẫn khởi động container.
Nhìn chung, chúng tôi rất hài lòng với những con số hiệu suất này. Khi các agent thống trị thế giới, rõ ràng chúng tôi có thể mở rộng quy mô cùng với chúng.
Tự mình trải nghiệm
Sớm thôi, hệ thống mới này sẽ hỗ trợ tất cả việc lập lịch sandbox tại Modal, nhưng nó đã có sẵn trong Beta. Bạn muốn tham gia với một thay đổi đơn giản trong mã của bạn. Nếu bạn cần chạy nhiều sandbox, hãy dùng thử và nói chuyện với chúng tôi!
Lời cảm ơn
Rất nhiều người đã đóng góp mồ hôi, nước mắt và công sức cho dự án này. Bản POC ở Miami của chúng tôi được xây dựng bởi Colin Weld (tôi), Daniel Shaar, Walter Tang và Gleb Posobin, và sau đó được đưa vào sản xuất bởi Walter, Colin, Connor Adams, Akshay Balwally, Tom Wildenhain, Scott Hao và Taylor Baldwin.





