Trăn đá

Python được quảng cáo rộng rãi là một trong những ngôn ngữ thân thiện với nhà phát triển nhất, nhờ vào vòng phản hồi nhanh đến từ việc không cần biên dịch. Tuy nhiên, khi được sử dụng trên quy mô lớn trong Máy chủ Instagram, chúng tôi nhận thấy một vấn đề lớn về khả năng sử dụng khi phát triển cục bộ; . ] để quan sát tác động của sự thay đổi của chúng

Tại Meta, chúng tôi đã giải quyết vấn đề này bằng cách tạo Lazy Imports. một tính năng thời gian chạy Python cung cấp một cơ chế rõ ràng và mạnh mẽ để tải chậm. Sử dụng kỹ thuật này, chúng tôi đã tiết kiệm hàng trăm giờ cho nhà phát triển mỗi ngày bằng cách giảm ~70% chi phí tải lại, cho phép nhà phát triển lặp lại nhanh hơn

Một tách cà phê lạnh

Tất cả bắt đầu vào một buổi sáng. Bạn thức dậy, rót cho mình một tách cà phê nóng và cắm đầu vào laptop để bắt đầu một ngày làm việc hiệu quả. Bạn có rất nhiều ý tưởng tuyệt vời về những điều bạn sẽ hoàn thành trong ngày. Bạn khởi động lại và máy chủ đang tải lại trong khi bạn nhấp một ngụm cà phê. ngày bắt đầu

Như thường lệ, bạn chỉnh sửa một vài tệp nên máy chủ cần tải lại. Phải mất một thời gian để khởi động lại và tất cả chúng tôi đều ổn. cho đến khi. có lỗi này gây ra lỗi cho bạn, một trong những điều mơ hồ mà bạn không biết gì hoặc nó đến từ đâu. Bạn cần thêm một số nhật ký để sửa đổi một trong các tệp được liệt kê trong truy nguyên. mười giây, hai mươi, sáu mươi giây. máy chủ vẫn đang tải lại. nổ. Lỗi cú pháp trong dòng nhật ký của bạn. Bạn fix lỗi rồi lưu file lại. máy chủ bắt đầu tải lại. tải lại. tải lại một số chi tiết. Sau hai phút, bạn đã sẵn sàng để xem nhật ký của mình. Một giờ sau, cuối cùng bạn cũng tìm ra lỗi, đó là một dòng mà bạn đã xóa hai ngày trước, một dòng import, không may đã kích hoạt một chu kỳ nhập khó hiểu sau khi tìm nạp và khởi động lại mã mới nhất

Đến thời điểm này, bạn vừa đốt cháy vài giờ buổi sáng của mình và bị phân tâm khỏi những việc đáng lẽ phải hoàn thành trong ngày hôm nay. Tệ nhất là cà phê của bạn bây giờ đã nguội

Bạn nhận được hình ảnh; . Điều đó tăng lên nhanh chóng. Chẳng mấy chốc phút trở thành giờ và giờ trở thành ngày, tất cả thời gian bị lãng phí

Tải lại máy chủ chậm

Khởi động Máy chủ Instagram, chúng tôi dành nhiều thời gian để tải các mô-đun. Thông thường, các mô-đun được kết hợp chặt chẽ với nhau, điều này khiến cho việc ngăn Hiệu ứng Domino Nhập khi nhập bất kỳ thứ gì trở nên khó khăn

Tải lại máy chủ mất khoảng 25 giây vào cuối năm 2021. Thời gian này trong lịch sử đã liên tục thụt lùi — một trận chiến không ngừng trong nhiều năm. Nếu chúng tôi không chú ý đến việc giữ cho nó được tối ưu hóa, thời gian tải lại sẽ tăng lên nhanh chóng; . Vào lúc cao điểm, vào cuối năm, một số lần tải lại mất tới 1. 5 phút. Thật không may, đây là khoảng thời gian hoàn hảo để các kỹ sư bị phân tâm bởi thứ gì đó lấp lánh và quên mất họ đang làm gì

Tại sao máy chủ quá chậm?

Độ phức tạp của codebase

Lý do chính khiến tải chậm là cơ sở mã ngày càng phức tạp mà chúng tôi có trong Instagram, cùng với thực tế là chúng tôi có rất nhiều mô-đun tạo ra nhiều tham chiếu

Nếu bạn chưa bao giờ xem hình ảnh biểu đồ phụ thuộc của mã Máy chủ Instagram phức tạp như thế nào, thì Joshua Lear đã dành cả ngày để chuẩn bị một hình ảnh. Sau 3 giờ chạy tập lệnh trực quan hóa phụ thuộc đã sửa đổi, anh ấy quay lại một "quả bóng lớn, màu đen. " Lúc đầu, anh ấy nghĩ rằng bộ phân tích phụ thuộc có lỗi, nhưng hóa ra biểu đồ phụ thuộc của Máy chủ Instagram là một vòng tròn khổng lồ

Giải trí [diễn giải nghệ thuật] của Đồ thị phụ thuộc Instagram, bởi Joshua Lear

Trên thực tế, biểu đồ phụ thuộc trong cơ sở mã Instagram là một lưới lớn xấu xí; . Chỉ cần khởi động máy chủ sẽ tự động kích hoạt tải một số lượng lớn các mô-đun, khoảng 28.000 và phần lớn thời gian khởi động đó được sử dụng, theo nghĩa đen, chỉ nhập các mô-đun, tạo các hàm Python và các đối tượng lớp. Biểu đồ phụ thuộc trông đẹp hơn lần đầu tiên được cung cấp bởi Benjamin Woodruff và được cập nhật để phản ánh trạng thái hiện tại

Biểu đồ phụ thuộc Instagram thực, tháng 1 năm 2022

Vậy vấn đề là gì?

Nhập Khẩu Thông Tư

Mã cực kỳ phức tạp và các phụ thuộc đan xen là công thức dẫn đến thảm họa. Tái cấu trúc để giữ cho các phụ thuộc sạch sẽ và tối thiểu nghe có vẻ như là cách khắc phục rõ ràng, nhưng điểm vướng mắc lớn nhất là nhập khẩu tuần hoàn. Ngay khi bạn bắt đầu cố gắng cấu trúc lại, các chu trình nhập sẽ xuất hiện ở khắp mọi nơi

Các chu kỳ nhập khẩu làm cho việc tái cấu trúc trở nên khó khăn hơn và đã từng gây ra một số sự cố ngừng hoạt động;

Một chùm ánh sáng

Trước đây, chúng tôi đã cố gắng cấu trúc lại các mô-đun để phá vỡ các chu kỳ nhập và đơn giản hóa biểu đồ phụ thuộc. Chúng tôi đã thử điều chỉnh cẩn thận các giải pháp bằng cách làm cho các hệ thống con đắt tiền trở nên lười biếng, e. g. , Django Urls, Thông báo, Người quan sát, thậm chí cả Biểu thức chính quy. Điều này hoạt động ở một mức độ nhất định, nhưng tạo ra các giải pháp mong manh. Trong nhiều năm, vô số thời gian đã được dành để cố gắng giải quyết vấn đề này bằng cách lập hồ sơ thủ công, tái cấu trúc và dọn dẹp mọi thứ, chỉ để nhận ra rằng nhiều thứ sẽ sớm bị lãng phí khi mã và độ phức tạp tiếp tục tăng lên. Quá trình này khó khăn, mong manh và không mở rộng tốt

Những gì chúng tôi cần là một cách mạnh mẽ để lười biếng tất cả mọi thứ

Con lười hai ngón do Geoff Gallice cung cấp thông qua Creative Commons

Chúng tôi cần một cách minh bạch hơn, tự động, đáng tin cậy và lâu dài hơn để khiến mọi thứ trở nên lười biếng, thay vì cố gắng làm mọi thứ trở nên lười biếng theo cách thủ công bằng cách sử dụng nhập khẩu bên trong, import_module[] hoặc __import__[]. Dự án được hình dung đầy tham vọng và rủi ro, nhưng tôi đã xắn tay áo, tìm hiểu sâu về CPython và bắt đầu triển khai Lazy Imports trong Cinder

Lazy Imports thay đổi cơ chế về cách hoạt động của quá trình nhập trong Python để các mô-đun chỉ được nhập khi chúng được sử dụng. Về cốt lõi, mỗi lần nhập [e. g. , import foo] sẽ không tải và thực thi mô-đun ngay lập tức, thay vào đó, mô-đun sẽ tạo một tên "đối tượng bị trì hoãn". Trong nội bộ, tên đó sẽ vẫn là một phiên bản của đối tượng hoãn lại cho đến khi tên đó được sử dụng, tên này có thể ở dòng tiếp theo sau khi nhập hoặc trong ngăn xếp cuộc gọi sâu, nhiều giờ sau đó

Sau một vài tuần làm việc với nó, tôi đã có thể có được một nguyên mẫu. Nó hoạt động tốt và rất hứa hẹn; . Phần khó là làm cho mọi thứ trở nên chắc chắn, làm cho việc triển khai trở nên siêu hiệu quả và triển khai nó mà không gặp quá nhiều trục trặc. Việc thay đổi ngữ nghĩa Python, cách mà tính năng này thực hiện, sẽ phức tạp hơn nhiều so với tôi nghĩ ban đầu và có rất nhiều vấn đề không mong muốn cần khám phá và khắc phục trong quá trình thực hiện

Có rất nhiều điều kỳ quặc và sắc thái trong cách Python hoạt động bên trong và các đối tượng trì hoãn Nhập khẩu lười biếng bất ngờ bị rò rỉ ra khỏi thế giới C vào Python. Sau một số cuộc thảo luận rất hiệu quả với Carl Meyer và Dino Viehland, tôi quyết định thiết kế lại bộ máy và chuyển phần lớn nó vào sâu hơn, vào trung tâm của Python. nội bộ từ điển. Tôi đã rất hào hứng, nhưng việc sửa đổi cách triển khai từ điển được tối ưu hóa cao có thể dẫn đến một hình phạt thực sự kém về hiệu suất, vì vậy tôi đã rất quan tâm đến phần này và việc tối ưu hóa mất một khoảng thời gian khá lớn

Cuối cùng, tôi đã có thể có được một phiên bản đáng tin cậy và hiệu quả. Tôi đã bật Nhập từng phần trong hàng chục nghìn mô-đun Máy chủ Instagram và bắt đầu chạy thử nghiệm hiệu suất trên đó để xem liệu nó có tạo ra bất kỳ sự khác biệt nào về hiệu suất trong quá trình sản xuất hay không [không nên]. Chắc chắn rồi, mạng trông gần giống như một bản wash, chúng tôi không thấy bất kỳ dấu hiệu rõ ràng nào cho thấy việc triển khai sẽ ảnh hưởng tiêu cực đến quá trình sản xuất và cuối cùng tôi cũng có một bản dựng trung tính hoàn hảo

Kết quả

Vào đầu tháng 1 năm 2022, chúng tôi đã triển khai cho hàng nghìn máy chủ sản xuất và phát triển mà không gặp sự cố lớn nào, đồng thời chúng tôi có thể thấy ngay sự khác biệt về thời gian bắt đầu của Máy chủ Instagram trong biểu đồ

Bằng cách tải các mô-đun ít hơn ~12 lần, chúng tôi đã giảm được ~70% thời gian tải lại p50 và giảm ~60% thời gian tải lại p90 cho các máy chủ phát triển Instagram. Đồng thời, nó hầu như loại bỏ tất cả các sự kiện lỗi chu trình nhập mà chúng tôi thấy hàng ngày. Các máy chủ và công cụ khác liên tục thấy sự cải thiện từ 50% đến 70% và mức sử dụng bộ nhớ giảm từ 20% đến 40%

Bạn có đoán được thời điểm Lazy Imports được bật trong biểu đồ không?

Xem thêm kết quả tại đây

thử thách

Trên đường đi, tôi gặp rất nhiều chướng ngại vật, quá nhiều để liệt kê trong bài đăng này. Một số phức tạp hơn những cái khác, nhưng tất cả chúng đều thú vị và đầy thách thức. Tôi có thể nhớ lại một số lỗi trong CPython [bpo-41249, liên quan đến TypedDict], một số thư viện mà tôi đã phải xóa và rất nhiều bài kiểm tra mà tôi đã phải sửa

Trong hành trình tạo cơ sở mã của tôi tương thích với Nhập khẩu lười biếng, các sự cố phổ biến hơn khi chúng tôi bắt đầu sử dụng Nhập khẩu lười biếng là

  1. Liên quan đến các mô-đun dựa vào Tác dụng phụ Nhập khẩu
    • Mã thực thi bất kỳ logic nào khi được nhập
    • Dựa vào các mô-đun con được đặt làm thuộc tính trong các mô-đun chính
  2. Các vấn đề liên quan đến đường dẫn Python động;
  3. Tất cả các lỗi được hoãn lại từ lần nhập đến lần sử dụng đầu tiên [bao gồm cả ModuleNotFoundError], điều này có thể làm phức tạp quá trình gỡ lỗi
  4. Cần cẩn thận khi áp dụng các chú thích loại nếu không nó có thể vô tình đánh bại Nhập khẩu lười biếng
    • Các mô-đun nên sử dụng from __future__ import annotations.
    • Chúng ta nên sử dụng chú thích kiểu chuỗi chotyping.TypeVar[]typing.NewType[]
    • Bọc bí danh loại bên trong khối điều kiện import_module[]0

Đối với các vấn đề và vấn đề toàn diện hơn, xem tại đây

Điểm nổi bật

Mặc dù khái niệm về lazy import không hoàn toàn mới và đơn giản về mặt khái niệm [i. e. , trì hoãn tải mô-đun cho đến khi tên đã nhập được sử dụng], chúng tôi không biết về bất kỳ triển khai cấp thấp nào khác trực tiếp trong nội bộ CPython và không có nỗ lực nào trước đây phù hợp với triển khai hiện tại của chúng tôi trong Cinder. Một số điểm nổi bật của nó là

  • Nó cung cấp một giải pháp tự động, mạnh mẽ và gần như minh bạch cho mô hình thường được sử dụng để làm cho mọi thứ trở nên lười biếng trong Python
  • Nó cần ít nỗ lực để được sử dụng. Chúng tôi có thể bật Nhập khẩu lười biếng trên toàn cầu, dưới dạng một tính năng ở cấp độ ngôn ngữ và để Python tải mọi mô-đun và gói từng được sử dụng một cách lười biếng [thậm chí cả gói thư viện tiêu chuẩn và bên thứ ba]
  • Nó hiệu quả.
    Chúng tôi đã chạy một loạt thử nghiệm trong các máy chủ trực tiếp của mình và kết quả là hiệu suất trung lập khi thêm bản vá Lazy Import [nhưng không bật tính năng này].
    Chúng tôi cũng đã chạy mã nguồn mở import_module[]1 3 lần và quan sát thấy các kết quả quan trọng nhất sau đây khi bật tính năng Nhập lười biếng so với. không có bản vá.
  • Không còn chu kỳ nhập khẩu. Điều đó không có nghĩa là không thể nhập vòng tròn khi bật Lazy Import. Vẫn có thể có các phụ thuộc chu kỳ hợp pháp ở cấp độ mô-đun, nhưng hầu hết các chu kỳ sẽ không có hại và sẽ không biểu hiện dưới dạng lỗi nhập. Trong trường hợp sử dụng của chúng tôi tại Instagram, chúng tôi đã chuyển từ các kỹ sư thấy ~80 lỗi nhập vòng tròn mỗi ngày thành 0
  • Nó chỉ hoạt động™️ [hầu hết thời gian]

Những gì nằm ở phía trước

  • Với lượng khởi động phù hợp, Lazy Imports chắc chắn sẽ mang lại cho chúng tôi một số lợi ích trong việc sử dụng bộ nhớ, thời gian khởi động và có lẽ [hy vọng] thậm chí sẽ đạt được một số hiệu suất trong các máy chủ sản xuất của Instagram
  • Không phải lo lắng về Nhập khẩu tuần hoàn, Nhập khẩu lười biếng mở ra một con đường hoàn toàn mới để hiện đại hóa và cải thiện chất lượng của cơ sở mã. Tái cấu trúc trở nên dễ dàng hơn nhiều và những điều từng là không thể giờ đây có thể thực hiện được
  • Làm việc với các gói và thư viện của bên thứ ba bên ngoài để chúng thân thiện với việc nhập chậm, giúp nhiều ứng dụng khác có thể tận dụng khả năng này
  • Upstreaming Lazy Imports để cung cấp nó cho hệ sinh thái Python rộng lớn hơn

Cảm ơn

Dự án này là một công việc to lớn, nó không thể thực hiện được nếu không có sự giúp đỡ của nhiều kỹ sư, những người đã dành thời gian và nỗ lực của họ cho một điều gì đó to lớn. Tôi thực sự muốn dành thời gian để cảm ơn tất cả mọi người đã tham gia, vì những đánh giá và đề xuất về mã của bạn. Để được trợ giúp triển khai và phổ biến việc sử dụng Nhập khẩu lười biếng ngoài Instagram. Đối với các ý tưởng, đề xuất và đánh giá viết bài này. Anirudh Padmarao, Ben Green, Benjamin Woodruff, Carl Meyer, Dino Viehland, Itamar Ostricher, Jacky Zhang, Joshua Lear, Krys Jurgowski, Lisa Roach, Loren Arthur, Miguel Gaiowski, Perry Randall, Xiaoya Xiang và những người khác đã tham gia, xin cảm ơn

Để tìm hiểu thêm về Mã nguồn mở Meta, hãy truy cập trang web nguồn mở của chúng tôi, đăng ký kênh YouTube của chúng tôi hoặc theo dõi chúng tôi trên Twitter, Facebook và LinkedIn

Than trăn là gì?

Cinder là Phiên bản định hướng hiệu năng của Meta của CPython 3. 8 . Nó đã được sử dụng làm Python sản xuất đằng sau máy chủ Instagram trong nhiều năm, cũng như cung cấp năng lượng cho nhiều ứng dụng Python khác trên Meta.

CPython có phải là trình thông dịch không?

CPython có thể được định nghĩa là cả trình thông dịch và trình biên dịch vì nó biên dịch mã Python thành mã byte trước khi thông dịch nó. Nó có giao diện chức năng nước ngoài với một số ngôn ngữ, bao gồm cả C, trong đó người ta phải viết rõ ràng các ràng buộc bằng ngôn ngữ khác Python.

Chủ Đề