FDE PulseViệc làm FDE đang mở 316Mới đăng 7 ngày qua 10Chủ đề nổi bật: Đào tạo kỹ năng FDE tại Đông Nam Á

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

Độ trễ, thông lượng, khả năng mở rộng: trả lời đội hạ tầng của khách bằng số đo

Khi đội hạ tầng của khách hỏi "p99 bao nhiêu, mỗi giây chịu được bao nhiêu request", câu "chạy khá nhanh" sẽ làm bạn mất sự tin cậy của họ ngay trong buổi họp đầu.

Đồ hoạTừ "chạy khá nhanh" đến một câu trả lời có số đo
  1. 1Đo với một người dùngNếu đã chậm ở bước này thì đó là vấn đề performance: mở trace, chưa thêm server.
  2. 2Tăng tải dầnBắn 1, 4, 8, 16 request đồng thời vào một replica, ghi lại phân phối latency.
  3. 3Đọc p50, p99, request/giâyTìm điểm throughput chững lại trong khi latency vẫn tiếp tục tăng.
  4. 4Thêm replica rồi đo lạiThông lượng phải tăng theo tài nguyên, và dự phòng không được làm hệ thống chậm đi.
  5. 5Trả lời đội hạ tầngNói rõ X request/giây mỗi replica với p99 dưới Y giây, theo đúng ngưỡng khách đặt ra.

Phải xác nhận service nhanh với một người dùng trước, rồi mới đo tải và kiểm tra khả năng mở rộng.

Đồ hoạ: FDE Times

Tóm tắt nhanh

  • Đội hạ tầng nói chuyện bằng percentile và request mỗi giây, không nói bằng giá trị trung bình hay cảm giác "khá nhanh".
  • Chậm với một người dùng là vấn đề performance. Nhanh với một người nhưng chậm khi đông là vấn đề scalability.
  • Ở quy mô lớn, vài request chậm bất thường có thể chi phối cả dịch vụ, nên cần đo và thiết kế riêng cho phần đuôi.
Chia sẻLinkedInFacebookX

Bạn vừa deploy xong một service trích xuất dữ liệu hóa đơn tại công ty khách. Sang buổi họp review, trưởng nhóm hạ tầng của họ hỏi hai câu: “p99 bao nhiêu? Một instance chịu được bao nhiêu request mỗi giây?” Bạn trả lời rằng chạy thử thấy khá nhanh. Phòng họp im một lúc, và bạn hiểu là mình vừa bị xếp vào nhóm “đội vendor chưa đo”.

Tình huống này xảy ra với FDE thường hơn bạn nghĩ. Viết code chạy được là phần bạn quen tay. Thuyết phục những người sẽ trực đêm cho hệ thống đó rằng nó không sập lúc cao điểm lại là một kỹ năng khác. Để làm được việc đó, bạn cần nói đúng ba từ (latency, throughput, scalability), và mỗi từ phải đi kèm một con số.

Ba từ, ba câu hỏi khác nhau

Latency là thời gian hệ thống phản hồi một request. Throughput là lượng request hệ thống gánh được cùng lúc, trong thực tế thường quy ra số request mỗi giây. Tài liệu system design trên cs.fyi lưu ý rằng ở hầu hết hệ thống, hai đại lượng này đánh đổi cho nhau: dồn thêm việc vào thì mỗi việc thường phải chờ lâu hơn.

Vì có đánh đổi nên phải có mục tiêu. Jonas Bonér, trong bộ slide về các pattern scalability, đặt mục tiêu là đạt throughput tối đa với latency ở mức chấp nhận được. Thế nào là “chấp nhận được” thì phía khách quyết định, không phải bạn. Bởi vậy câu đầu tiên nên hỏi đội hạ tầng là ngưỡng latency của họ, chưa nên khoe con số của mình.

Từ thứ ba là scalability, và đây là chỗ hay bị nói lẫn với performance nhất. Một bài viết trên blog Professor Beekums tách hai khái niệm rất gọn: scalability là xử lý được lượng lớn người dùng, dữ liệu hay traffic, còn performance là chuyện tốc độ.

Tác giả lấy ví dụ quầy thu ngân: scalability là giữ cho tốc độ phục vụ không đổi dù khách đến đông hay vắng.

Werner Vogels, CTO của Amazon, định nghĩa chặt hơn: một service có khả năng mở rộng nếu khi thêm tài nguyên thì hiệu năng tăng theo. Với service luôn bật, ông thêm một điều kiện: tài nguyên thêm vào để dự phòng không được làm hiệu năng giảm.

Vogels cũng nói rõ “hiệu năng tăng” có thể hiểu theo hai cách. Thường đó là phục vụ được nhiều đơn vị công việc hơn, nhưng cũng có thể là xử lý được những đơn vị công việc lớn hơn. Khi đo tải, người ta hay chỉ nhớ cách hiểu đầu và quên mất cách thứ hai.

Chậm với một người hay chỉ chậm khi đông?

Bonér đưa ra một phép thử dễ dùng ngay. Nếu hệ thống chậm ngay cả với một người dùng, đó là vấn đề performance. Nếu nó nhanh với một người nhưng chậm khi tải cao, đó là vấn đề scalability. Hai loại cần hai cách sửa khác nhau, nên chẩn đoán sai thì công sức bỏ vào cũng phí.

Thử hình dung service hóa đơn của bạn mất 4 giây cho một request khi không có ai khác dùng. Thêm server lúc này không giúp gì, vì từng request vẫn mất 4 giây. Bạn phải mở trace, xem thời gian nằm ở OCR, ở lời gọi model hay ở truy vấn database. Chỉ khi một người dùng đã thấy nhanh thì mới đến lượt câu hỏi về tải.

Một bài đo hoàn chỉnh, từng bước

Giả sử service đã nhanh với một người dùng. Việc tiếp theo là bắn tải tăng dần vào một replica duy nhất và ghi lại phân phối latency, không chỉ ghi số trung bình. Đoạn script dưới đây đủ dùng cho lần đo đầu:

import asyncio, time, statistics, httpx

async def one(client, url, payload, out):
    t = time.perf_counter()
    await client.post(url, json=payload, timeout=60)
    out.append(time.perf_counter() - t)

async def run(url, payload, concurrency, total=200):
    lat, sem = [], asyncio.Semaphore(concurrency)
    async with httpx.AsyncClient() as c:
        async def task():
            async with sem:
                await one(c, url, payload, lat)
        start = time.perf_counter()
        await asyncio.gather(*[task() for _ in range(total)])
        wall = time.perf_counter() - start
    q = statistics.quantiles(lat, n=100)
    print(concurrency, round(q[49], 2), round(q[98], 2), round(total / wall, 1))

Chạy script với concurrency 1, 4, 8, 16 trên một replica. Sau đó thêm replica thứ hai sau load balancer, trỏ script vào load balancer và chạy lại với 16 request đồng thời, tức khoảng 8 cho mỗi replica. Bảng dưới đây là kết quả minh họa, không phải số đo thật, nhưng hình dạng của nó là thứ bạn sẽ gặp đi gặp lại:

Replica Request đồng thời p50 (giây) p99 (giây) Request/giây
1 1 1,2 1,5 0,8
1 4 1,3 1,9 3,0
1 8 1,6 3,8 5,0
1 16 3,2 9,5 5,0
2 16 1,6 3,9 9,5

Cách đọc bảng như sau. Từ 1 lên 8 request đồng thời, throughput tăng còn p50 chỉ nhích nhẹ, dù p99 đã bắt đầu dãn ra từ 1,5 lên 3,8 giây. Từ 8 lên 16, throughput đứng yên ở 5 request/giây trong khi p50 tăng gấp đôi và p99 vọt lên 9,5 giây. Replica đã bão hòa, và request dồn thêm chỉ đứng xếp hàng.

Dòng cuối là phép thử scalability theo đúng định nghĩa của Vogels. Cùng 16 request đồng thời, nhưng với 2 replica, throughput lên 9,5 request/giây, gần gấp đôi, còn p50 và p99 quay về gần mức của một replica ở 8 request. Thêm tài nguyên thì hiệu năng tăng tương ứng: service này đang mở rộng được.

Nếu dòng đó chỉ cho 6 request/giây thay vì gần 10, hai replica đang tranh nhau một tài nguyên chung, có thể là database, có thể là rate limit của API model. Khi ấy thêm replica thứ ba cũng vô ích cho đến khi bạn tìm ra điểm nghẽn đó.

Giả sử phía khách chốt ngưỡng p99 là 5 giây. Câu trả lời của bạn trong buổi họp sẽ là: “Một replica phục vụ khoảng 5 request/giây với p99 dưới 4 giây, nếu giữ khoảng 8 request đồng thời.

Hai replica đã đo được 9,5 request/giây ở cùng mức p99, nên muốn đạt 20 request/giây thì ước tính cần 4 replica, và sẽ đo lại ở 4 replica để xác nhận.” Đó là một câu đội hạ tầng có thể đem đi lập kế hoạch capacity.

Bạn cũng nên đo thêm một hóa đơn 50 trang, vì theo Vogels, xử lý được đơn vị công việc lớn hơn cũng là một chiều của scalability.

Cái đuôi phân phối mới là thứ làm bạn mất ngủ

Bảng trên có lý do để dùng p99 chứ không dùng trung bình. Uwe Friedrichsen, khi phân tích bài “The Tail at Scale” của Jeffrey Dean và Luiz Barroso, nhắc lại rằng ở quy mô lớn, những đợt latency cao tạm thời có thể chi phối hiệu năng của toàn bộ dịch vụ.

Ông cũng nhận xét rằng dịch vụ càng thành công thì càng chịu ảnh hưởng của phần tail latency còn sót lại.

Một phép tính nhỏ cho thấy vì sao. Giả sử một request của người dùng phải gọi song song 100 service con, và mỗi service con chỉ chậm bất thường 1% số lần. Xác suất có ít nhất một lời gọi chậm là 1 − 0,99^100, khoảng 63%, nên phần đuôi của từng thành phần đã thành trường hợp phổ biến của cả hệ thống.

Friedrichsen mô tả một kỹ thuật để cắt phần đuôi này: hedged request, tức gửi cùng một request tới vài replica và dùng phản hồi về sớm nhất.

Gửi nhiều bản sao đồng nghĩa với việc tăng tải, nên khi áp dụng, cách làm thận trọng là chỉ gửi bản sao khi request đầu đã vượt một ngưỡng, chẳng hạn p95 bạn đo được. Và khi đề xuất kỹ thuật này với đội hạ tầng, hãy nói luôn nó tốn thêm bao nhiêu phần trăm tải.

Những lỗi khiến đội hạ tầng mất tin

Lỗi phổ biến nhất là báo số trung bình. Lỗi thứ hai là đo trên laptop với một người dùng rồi gọi kết quả là “hiệu năng production”, tức là trộn performance với scalability. Lỗi thứ ba là thêm replica để dự phòng mà không kiểm tra lại: theo tiêu chí của Vogels, nếu dự phòng làm hệ thống chậm đi thì service đó chưa đạt yêu cầu.

Còn một lỗi tinh vi hơn: chỉ đưa ra con số throughput mà không kèm ngưỡng latency. Câu “chịu được 5 request/giây” mà không nói p99 ở mức đó là bao nhiêu thì gần như vô nghĩa, vì ở 16 request đồng thời hệ thống vẫn “chịu được”, chỉ là mỗi người phải đợi gần 10 giây.

Nếu bạn đang nhắm tới vai trò FDE, một dòng CV như “đo tải và lập kế hoạch capacity cho service X: 5 req/s mỗi replica ở p99 dưới 4 giây, tăng gần tuyến tính khi thêm replica” thuyết phục hơn nhiều so với “tối ưu hiệu năng hệ thống”.

Gặp job description nhắc tới việc đưa hệ thống lên production hay làm việc với đội hạ tầng của khách, hãy mang sẵn một bảng đo như trên vào buổi phỏng vấn.

Lần tới khi đội hạ tầng của khách hỏi p99, bạn không cần thuộc định nghĩa. Bạn chỉ cần mở một bảng vài dòng số do chính mình đo.

5 nguồn
Đọc tiếp trên lộ trình · Chặng 2: Kỹ thuật rộngĐịnh lý CAP cho FDE: vì sao trợ lý AI vẫn nói đơn hàng chưa hủyKhi trợ lý AI trả lời bằng dữ liệu cũ hơn hệ thống của khách, lỗi thường không nằm ở model mà ở mức nhất quán chưa ai chọn một cách có chủ đích.