Bởi James Beswick | vào ngày 20 tháng 4 năm 2023 | trong AWS Lambda, Serverless
Các ứng dụng serverless có thể giảm tổng chi phí sở hữu (TCO) khi so sánh với mô hình thực thi trên đám mây dựa trên máy chủ, vì nó chuyển các trách nhiệm vận hành như quản lý server sang nhà cung cấp cloud một cách hiệu quả. Nghiên cứu của Deloitte về TCO serverless với các khách hàng Fortune 100 trên các ngành công nghiệp chỉ ra rằng, các ứng dụng serverless có thể tiết kiệm tới 57% chi phí so với các giải pháp dựa trên server.
Các ứng dụng serverless có thể mang lại chi phí thấp hơn trong:
Phát triển ban đầu (initial development): Serverless cho phép nhà phát triển trở nên linh hoạt hơn, , triển khai các tính năng nhanh chóng hơn và tập trung vào sự khác biệt trong logic kinh doanh.
Cơ sở hạ tầng và bảo trì liên tục (ongoing maintenance and infrastructure): Serverless chuyển gánh nặng vận hành sang AWS. Với sự bảo trì liên tục, bao gồm vá lỗi và cập nhật hệ điều hành.
Bài đăng này tập trung vào các tùy chọn có sẵn để giảm chi phí AWS khi xây dựng các ứng dụng serverless. AWS Lambda thường là lớp điện toán trong các workloads này và có thể chiếm một phần đáng kể trong tổng chi phí.
Để giúp tối ưu hóa chi phí liên quan đến Lambda, ta sẽ thảo luận về một số kỹ thuật tối ưu hóa chi phí được sử dụng phổ biến nhất, đặc biệt là những thay đổi về configuration khi cập nhật code. Bài đăng này dành cho các kiến trúc sư và nhà phát triển mới làm quen với việc xây dựng bằng serverless.
Xây dựng bằng serverless giúp cả việc thử nghiệm và cải tiến lặp lại trở nên dễ dàng hơn. Tất cả các kỹ thuật được mô tả ở đây có thể được áp dụng trước khi phát triển ứng dụng hoặc sau khi bạn đã triển khai ứng dụng của mình vào production. Các kỹ thuật đại khái dựa trên khả năng ứng dụng: Kỹ thuật đầu tiên có thể áp dụng cho bất kỳ workload nào; kỹ thuật cuối cùng áp dụng cho số lượng workload nhỏ hơn.
Điều chỉnh kích thước phù hợp cho các Lambda functions của bạn
Lambda sử dụng mô hình trả cho mỗi lần sử dụng (pay-per-use) được điều khiển bởi ba chỉ số:
Cấu hình bộ nhớ: bộ nhớ được phân bổ từ 128MB đến 10.240MB. CPU và các tài nguyên khác có sẵn cho function được cấp tương ứng với bộ nhớ.
Thời lượng của function : thời gian function chạy, đo bằng mili giây.
Số lần được gọi: số lần function của bạn chạy.
Việc cung cấp bộ nhớ quá mức là một trong những nguyên nhân chính khiến chi phí Lambda tăng lên. Điều này đặc biệt nghiêm trọng đối với những người mới sử dụng Lambda, những người đã quen với việc cung cấp server chạy multiple processes. Lambda điều chỉnh quy mô sao cho mỗi môi trường thực thi của một function chỉ xử lý một yêu cầu tại một thời điểm. Mỗi môi trường thực thi có quyền truy cập vào các tài nguyên cố định (memory, CPU, storage) để hoàn thành công việc theo yêu cầu.
Bằng cách điều chỉnh size phù hợp cấu hình memory, function sẽ có các tài nguyên để hoàn thành công việc của mình và bạn chỉ phải trả tiền cho những tài nguyên cần thiết. Mặc dù bạn cũng có quyền kiểm soát trực tiếp thời lượng của function nhưng đây là cách tối ưu hóa chi phí kém hiệu quả hơn để thực hiện. Chi phí kỹ thuật để tiết kiệm được vài mili giây có thể lớn hơn chi phí tiết kiệm được. Tùy thuộc vào workload, số lần function của bạn được gọi có thể nằm ngoài tầm kiểm soát của bạn. Phần tiếp theo thảo luận về kỹ thuật giúp giảm số lượng lệnh gọi đối với một số loại workload.
Bạn có thể cấu hình bộ nhớ thông qua AWS Management Console hoặc tùy chọn infrastructure as code (IaC). Cấu hình bộ nhớ xác định bộ nhớ được cấp, không phải bộ nhớ được function của bạn sử dụng. Bộ nhớ có kích thước phù hợp có thể giảm chi phí (hoặc có thể tăng hiệu suất) cho function của bạn. Tuy nhiên, việc giảm bộ nhớ của function không phải lúc nào cũng giúp tiết kiệm chi phí. Giảm bộ nhớ function có nghĩa là giảm CPU có sẵn cho Lambda Function, điều này có thể làm tăng thời gian chạy function, dẫn đến không tiết kiệm được chi phí hoặc chi phí cao hơn. Điều quan trọng là xác định được cách cấu hình bộ nhớ thật tối ưu để tiết kiệm chi phí trong khi vẫn duy trì hiệu suất.
AWS cung cấp hai phương pháp phân bổ bộ nhớ với kích thước phù hợp: AWS Lambda Power Tuning và AWS Computer Optimizer.
AWS Lambda Power Tuning là một công cụ mã nguồn mở có thể được sử dụng để tìm ra cấu hình bộ nhớ để tối ưu cho function của bạn theo kinh nghiệm bằng cách đánh đổi giữa chi phí và thời gian thực hiện. Công cụ này chạy nhiều phiên bản đồng thời của function dựa trên dữ liệu đầu vào mô phỏng ở các mức phân bổ bộ nhớ khác nhau. Kết quả là một biểu đồ có thể giúp bạn tìm ra “điểm phù hợp” giữa chi phí và thời lượng/hiệu suất. Tùy thuộc vào workloads, bạn có thể ưu tiên cái này hơn cái kia. AWS Lambda Power Tuning là một lựa chọn phù hợp cho các function mới và cũng có thể giúp lựa chọn giữa hai kiến trúc tập lệnh do Lambda cung cấp.
AWS Compute Optimizer sử dụng machine learning để đề xuất cấu hình bộ nhớ tối ưu dựa trên dữ liệu lịch sử. Compute Optimizer yêu cầu function của bạn phải được gọi ít nhất 50 lần trong 14 ngày để đưa ra đề xuất dựa trên mức sử dụng trước đây, cách này hiệu quả nhất khi function của bạn được đưa vào production.
Cả Lambda Power Tuning và Compute Optimizer đều giúp đưa ra cách phân bổ bộ nhớ có kích thước phù hợp cho function của bạn. Sử dụng giá trị này để cập nhật cấu hình function của bạn bằng AWS Management Console hoặc IaC.
Trong bài đăng này bao gồm sample code của AWS Serverless Application Model (AWS SAM) trình bày cách triển khai tối ưu hóa. Bạn cũng có thể sử dụng Bộ công cụ phát triển đám mây AWS Cloud Development Kit (AWS CDK), Terraform, Serverless Framework, để triển khai những thay đổi tương tự.
“`YAML
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs18.x
Handler: app.handler
MemorySize: 1024 # Set memory configuration to optimize for cost or performance
“`
Đặt thời gian chờ function thực tế
Các Lambda Function được định cấu hình với thời gian tối đa mà mỗi lệnh gọi (invocation) có thể chạy, tối đa 15 phút. Việc đặt timeout thích hợp có thể hữu ích trong việc giảm chi phí cho ứng dụng dựa trên Lambda của bạn. Với các trường hợp như: exceptions chưa được xử lý, chặn các hành động (ví dụ: mở kết nối mạng), dependencies chậm và các tình trạng khác có thể dẫn đến các function chạy lâu hơn hoặc các function chạy cho đến khi hết timeout đã được cấu hình. Timeout thích hợp là cách bảo vệ tốt nhất chống lại code chậm và code sai.
Chúng tôi recommendation là đặt timeout dưới 29 giây cho tất cả các synchronous invocations hoặc những yêu cầu mà caller đang chờ được response. Timeout dài hơn phù hợp với các asynchronous invocations, với timeout dài hơn 1 phút là một exception cần được xem xét và kiểm tra.
Sử dụng Graviton
Lambda cung cấp hai instruction set architectures ở hầu hết các Khu vực AWS: x86 và arm64.
Chọn Graviton có thể tiết kiệm tiền theo hai cách. Đầu tiên, các function của bạn có thể chạy hiệu quả hơn nhờ kiến trúc Graviton2. Thứ hai, bạn có thể trả ít hơn cho thời gian chạy của chúng. Các Lambda Function do Graviton2 cung cấp được thiết kế để mang lại hiệu suất tốt hơn tới 19% với chi phí thấp hơn 20%. Hãy cân nhắc bắt đầu với Graviton khi phát triển các Lambda Function mới, đặc biệt là những function không yêu cầu các tệp nhị phân được biên dịch native.
Nếu function của bạn dựa trên các tệp nhị phân được biên dịch native hoặc được đóng gói dưới dạng container image, bạn phải rebuild để di chuyển giữa arm64 và x86. Các lớp Lambda cũng có thể bao gồm các dependencies cho kiến trúc này hoặc kiến trúc kia. Chúng tôi khuyến khích bạn xem lại các dependencies và kiểm tra function của mình trước khi thay đổi kiến trúc. Công cụ AWS Lambda Power Tuning cũng cho phép bạn so sánh giá cả và hiệu suất của arm64 và x86 ở các cài đặt bộ nhớ khác nhau.
Bạn có thể sửa đổi cấu hình kiến trúc của function trong bảng điều khiển hoặc IaC mà bạn chọn. Ví dụ: trong AWS SAM:
“`YAML
MyFunction:
Type: AWS::Serverless::Function
Properties:
Architectures:
– arm64 # Set architecture to use Graviton2
Runtime: nodejs18.x
Handler: app.handler
“`
Lọc các sự kiện đến
Lambda được tích hợp với hơn 200 nguồn sự kiện, bao gồm Amazon SQS, Amazon Kinesis Data Streams, Amazon DynamoDB Streams, Amazon Managed Streaming for Apache Kafka và Amazon MQ. Dịch vụ Lambda tích hợp với các nguồn sự kiện này để truy xuất thông báo và gọi function của bạn khi cần để xử lý các thông báo đó.
Khi làm việc với một trong những nguồn sự kiện này, người xây dựng có thể cấu hình bộ lọc (filter) để giới hạn các sự kiện được gửi đến function của bạn. Kỹ thuật này có thể giảm đáng kể số lần function của bạn được gọi, nó tùy thuộc vào số lượng sự kiện và độ chính xác của các bộ lọc của bạn. Khi bạn không sử dụng bộ lọc sự kiện, function phải được gọi trước để xác định xem một sự kiện có nên được xử lý hay không trước khi thực hiện công việc thực sự. Tính năng lọc sự kiện giúp giảm bớt nhu cầu thực hiện việc kiểm tra trước này đồng thời giảm số lượng invocations.
Ví dụ: bạn chỉ muốn một function chạy khi tìm thấy các đơn hàng trên 200 USD trong tin nhắn trên luồng dữ liệu Kinesis. Bạn có thể cấu hình pattern lọc sự kiện bằng bảng điều khiển hoặc IaC theo cách tương tự như cấu hình bộ nhớ.
Để triển khai filter luồng Kinesis bằng AWS SAM:
“`YAML
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs18.x
Handler: app.handler
Events:
Type: Kinesis
Properties:
StartingPosition: LATEST
Stream: “arn:aws:kinesis:us-east-1:0123456789012:stream/orders”
FilterCriteria:
Filters:
– Pattern: ‘{ “data” : { “order” : { “value” : [{ “numeric”: [“>”, 200] }] } } }’
“`
Nếu một sự kiện đáp ứng một trong các bộ lọc sự kiện được liên kết với nguồn sự kiện, Lambda sẽ gửi sự kiện đó đến function của bạn để xử lý. Nếu không, sự kiện sẽ bị loại bỏ do được xử lý thành công mà không gọi function.
Nếu bạn đang xây dựng hoặc chạy hàm Lambda được gọi bởi một trong các nguồn sự kiện đã đề cập trước đó, bạn nên xem lại các tùy chọn filter có sẵn. Kỹ thuật này không yêu cầu thay đổi code đối với Lambda function của bạn – ngay cả khi function thực hiện một số kiểm tra tiền xử lý, kiểm tra đó vẫn hoàn tất thành công khi triển khai tính năng filter.
Để tìm hiểu thêm, hãy đọc Filtering event sources for AWS Lambda functions.
Tránh đệ quy
Bạn có thể quen với khái niệm lập trình về hàm đệ quy hoặc hàm/quy trình gọi chính nó. Mặc dù hiếm gặp nhưng đôi khi khách hàng vô tình xây dựng đệ quy trong kiến trúc của họ, do đó, hàm Lambda liên tục gọi chính nó.
Mẫu đệ quy phổ biến nhất là giữa Lambda và Amazon S3. Các hành động trong vùng lưu trữ S3 có thể kích hoạt hàm Lambda và quá trình đệ quy có thể xảy ra khi Lambda function ghi lại vào cùng một bucket.
Hãy xem xét trường hợp: trong đó hàm Lambda được sử dụng để tạo thumbnail của hình ảnh do người dùng gửi. Bạn cấu hình bucket để kích hoạt chức năng tạo thumbnail khi một object mới được đặt vào bucket. Điều gì xảy ra nếu Lambda function lưu thumbnail vào cùng một bucket? Tiến trình sẽ bắt đầu lại và hàm Lambda sau đó sẽ chạy trên chính thumbnail. Đây là đệ quy và có thể dẫn đến tình trạng vòng lặp vô hạn.
Mặc dù có nhiều cách để ngăn chặn tình trạng này, nhưng cách tốt nhất là sử dụng S3 bucket thứ hai để lưu trữ thumbnail. Cách tiếp cận này giảm thiểu các thay đổi đối với kiến trúc vì bạn không cần thay đổi cài đặt thông báo cũng như primary S3 bucket. Để tìm hiểu về các phương pháp khác, hãy đọc Avoiding recursive invocation with Amazon S3 and AWS Lambda.
Nếu bạn gặp phải đệ quy trong kiến trúc của mình, hãy set Lambda reserved concurrency về 0 để ngăn function chạy. Chờ vài phút đến vài giờ trước khi nâng giới hạn concurrency. Vì các sự kiện S3 là asynchronous invocations có số lần thử lại tự động nên bạn có thể tiếp tục thấy đệ quy cho đến khi giải quyết được sự cố hoặc tất cả các sự kiện đã hết hạn.
Phần kết luận
Lambda cung cấp một số kỹ thuật mà bạn có thể sử dụng để giảm thiểu chi phí cơ sở hạ tầng cho dù bạn mới bắt đầu sử dụng Lambda hay đã triển khai nhiều function trong production. Khi kết hợp với initial development và ongoing maintenance, serverless có thể mang lại tổng chi phí sở hữu thấp. Bạn có thể thực hành những kỹ thuật này và hơn thế nữa với Serverless Optimization Workshop.
Để tìm hiểu thêm về kiến trúc serverless, tìm các patterns có thể tái sử dụng và cập nhật, hãy truy cập Serverless Land.
TAGS: contributed, serverless