Tối ưu hóa AWS Lambda: Sử dụng Extensions để xử lý Telemetry sau phản hồi
Tại Lead Bank, việc đồng bộ hóa telemetry đã vô tình biến các lỗi tắc nghẽn bộ xuất dữ liệu thành lỗi timeout 504 ảnh hưởng đến người dùng. Bài viết này chia sẻ cách sử dụng AWS Lambda Extensions kết hợp với goroutine trong Go để chuyển công việc flush ra khỏi đường dẫn phản hồi chính, giúp cải thiện độ trễ mà vẫn giữ nguyên khả năng quan sát hệ thống.

Tại Lead Bank, hạ tầng API được vận hành trên AWS Lambda phía sau API Gateway. Các hàm Lambda này phục vụ các điểm thanh toán quan trọng như chuyển tiền, séc và ACH, cũng như các endpoint tạo đối tượng cốt lõi như số dư và tài khoản. Vì đây là các API hướng tới người dùng và quan trọng về mặt vận hành, thời gian phản hồi là yếu tố then chốt.
Chúng tôi sử dụng AWS Distro for OpenTelemetry (ADOT) để thu thập traces, metrics và logs, sau đó chuyển tiếp đến Honeycomb. Tuy nhiên, khi đo lường các endpoint thực thể, chúng tôi nhận thấy mặc dù p50 (trung vị) thấp, nhưng p99 (độ trễ của 1% yêu cầu chậm nhất) lại không khả quan. Một tỷ lệ nhỏ yêu cầu thỉnh thoảng bị tăng vọt và chạm đến giới hạn timeout 10 giây của API Gateway, dẫn đến lỗi HTTP 504.
Bài viết này sẽ phân tích một chế độ thất bại cụ thể mà chúng tôi gặp phải, nơi việc flush telemetry đồng bộ đã biến các sự cố tắc nghẽn định kỳ thành timeout hiển thị với người dùng, và cách chúng tôi giải quyết vấn đề bằng cách sử dụng các nguyên ngữ đồng bộ hóa trong Golang và Lambda Extensions API của AWS.
Chế độ Thất bại: Đồng bộ hóa Telemetry
Vấn đề cốt lõi không nằm ở logic xử lý của hàm Lambda, mà ở việc chúng tôi thực hiện việc flush (đẩy dữ liệu) telemetry một cách đồng bộ trước khi trả về phản hồi.
Đoạn code ban đầu trông tương tự như sau:
func handler(ctx context.Context, request Request) (Response, error) {
response, err := processRequest(ctx, request) // Thường mất 25–200ms
// ForceFlush được dùng để giảm mất mát telemetry khi Lambda đóng băng môi trường.
// Đa số thời gian việc này nhanh, nhưng thỉnh thoảng sẽ bị treo 10 giây.
flushCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if flushErr := otelProvider.ForceFlush(flushCtx); flushErr != nil {
logger.Error("Error flushing telemetry", zap.Error(flushErr))
}
return response, err
}
Hầu hết các lần flush đều hoàn thành nhanh chóng. Tuy nhiên, thỉnh thoảng đường dẫn xuất dữ liệu sẽ bị tắc nghẽn do biến động mạng, áp lực ngược (backpressure) từ hạ tầng downstream hoặc các lần thử lại (retry). Vì việc flush nằm trên đường dẫn quan trọng (critical path), những sự cố hiếm gặp này trực tiếp gây ra timeout cho người dùng.
Khi một yêu cầu bị timeout tại API Gateway, thường thì handler đã hoàn thành logic kinh doanh, sau đó dành phần còn lại của 10 giây để chờ công việc liên quan đến flush. Điều này có nghĩa là chúng tôi đang timeout trong phần invocation vốn nên hữu ích về mặt vận hành, nhưng không nên hiển thị với người dùng.
Tại sao Lambda làm cho Việc này Khó khăn?
Trong một quy trình máy chủ truyền thống, bạn có thể trả về phản hồi HTTP và tiếp tục chạy ở nền vì quy trình vẫn tồn tại. Lambda thì khác. Môi trường runtime có thể bị đóng băng ngay sau khi handler trả về, và bất kỳ công việc nền nào bạn bắt đầu đều không được đảm bảo hoàn thành trừ khi bạn giữ cho môi trường đó hoạt động.
Nếu chúng tôi đơn giản loại bỏ ForceFlush(), chúng tôi sẽ thỉnh thoảng mất telemetry khi môi trường đóng băng, khiến việc gỡ rối sự cố trở nên khó khăn. Ngược lại, nếu giữ flush đồng bộ, chúng tôi chấp nhận rủi ro timeout.
Chúng tôi cần một cách để trả về phản hồi khi handler hoàn tất, sau đó flush telemetry sau phản hồi mà không mất cơ hội hoàn thành việc flush.
Giải pháp: AWS Lambda Extensions API
Lambda Extensions cung cấp một móc vòng đời (lifecycle hook) có thể ngăn môi trường đóng băng cho đến khi extension báo hiệu đã sẵn sàng cho sự kiện tiếp theo.
Sơ đồ luồng flush sau phản hồi sử dụng Lambda Extension
Cơ chế hoạt động ở mức độ cao như sau:
- Extension đăng ký qua POST tới
/extension/register. - Extension thực hiện một lệnh GET chặn (blocking) tới
/extension/event/next. - Lambda gửi các sự kiện INVOKE thông qua kết nối này.
- Extension gọi
/event/nextmột lần nữa khi đã sẵn sàng. - Lambda sẽ kiểm tra xem mọi extension đã gọi
/event/nextvà đang chờ chưa. Nếu chưa, container sẽ giữ nguyên trạng thái hoạt động.
Chúng tôi có thể xử lý lệnh gọi, trả về phản hồi, và trì hoãn việc gọi /event/next cho đến khi telemetry đã được flush xong.
Extensions thường là các quy trình riêng biệt được triển khai qua layer hoặc hình ảnh container dưới /opt/extensions, trong khi các internal extension chạy cùng tiến trình với runtime.
Triển khai với Go và Goroutines
Để triển khai giải pháp này, chúng tôi sử dụng các nguyên ngữ đồng thời (concurrency primitives) của Go như goroutines, channels và context.WithTimeout để điều phối việc chuyển giao một cách sạch sẽ và giới hạn thời gian chạy của flush.
Một cách tiếp cận ngây thơ là đăng ký làm extension và tạo một goroutine để thăm dò (poll) các sự kiện:
eventChannel := make(chan *Event, 1)
go func() {
for {
event, err := extensionClient.NextEvent(ctx)
if err != nil {
// Trong môi trường production, ghi log và đếm lỗi này; thử lại với backoff.
continue
}
// Giao sự kiện INVOKE cho wrapper của handler.
eventChannel <- event
}
}()
Tuy nhiên, điểm mấu chốt là quản lý việc gọi NextEvent(). Bạn cần đặt NextEvent() ở một vị trí duy nhất và không gọi lại nó cho đến khi flush hoàn tất. Điều này ngăn không cho bạn báo hiệu sẵn sàng trong khi quá trình dọn dẹp từ lệnh gọi trước vẫn đang chạy.
Bằng cách trả về phản hồi API ngay khi handler hoàn tất, sau đó flush telemetry sau đó, chúng tôi đảm bảo rằng các sự cố tắc nghẽn của bộ xuất dữ liệu không còn nằm trên đường dẫn yêu cầu.
Kết luận
Việc sử dụng Lambda Extensions cho phép chúng tôi trả về phản hồi ngay lập tức mà không hy sinh khả năng quan sát. Dưới áp lực tải cao, việc so sánh các điểm ngoại lệ về độ trễ của API Gateway với thời lượng telemetry.flush_trace đã xác nhận rằng việc flush sau phản hồi đã được loại bỏ khỏi đường dẫn quan trọng, không còn là nguyên nhân khiến các yêu cầu chạm đến giới hạn timeout 10 giây của gateway.
Bài viết liên quan

Phần mềm
Anthropic ra mắt Claude Opus 4.7: Nâng cấp mạnh mẽ cho lập trình nhưng vẫn thua Mythos Preview
16 tháng 4, 2026

Công nghệ
Qwen3.6-35B-A3B: Quyền năng Lập trình Agentic, Nay Đã Mở Cửa Cho Tất Cả
16 tháng 4, 2026

Công nghệ
Spotify thắng kiện 322 triệu USD từ nhóm pirate Anna's Archive nhưng đối mặt với bài toán thu hồi
16 tháng 4, 2026
