Ngăn chặn giới hạn băng thông: Cách khắc phục ProvisionedThroughputExceededException trong Kinesis

intermediate☁️ AWS2026-07-13| AWS SDK (Python, Node.js, Java), Kinesis Producer Library (KPL), AWS CLI

Error Message

ProvisionedThroughputExceededException: Rate exceeded for shard shardId-000000000000 in stream <stream-name> under account <account-id>.
#aws#kinesis#throttling#data-streams#cloudwatch

Thông báo lỗi

Bạn đang đẩy dữ liệu lên AWS Kinesis và đột nhiên các bản ghi log của bạn tràn ngập lỗi này:

ProvisionedThroughputExceededException: Rate exceeded for shard shardId-000000000000 in stream <stream-name> under account <account-id>.

Đây không chỉ là một cảnh báo; nó có nghĩa là Kinesis đang loại bỏ dữ liệu của bạn vì bạn đã chạm tới giới hạn hiệu suất tối đa.

Chuyện gì đang xảy ra?

Hãy coi mỗi shard trong stream của bạn như một đường ống chuyên dụng với kích thước cố định. AWS áp dụng hai giới hạn tốc độ nghiêm ngặt cho các đường ống này:

  • Băng thông nạp dữ liệu (Ingestion bandwidth): 1 MB mỗi giây.
  • Tần suất ghi (Write frequency): 1.000 bản ghi mỗi giây.

Vượt quá một trong hai giới hạn này, AWS sẽ điều tiết (throttle) các yêu cầu của bạn. Điều này thường xảy ra do tổng lưu lượng truy cập của bạn đã vượt quá số lượng shard, hoặc bạn đang gặp tình trạng "hot shard". Một "hot shard" xảy ra khi các partition key của bạn không được phân bổ tốt, khiến một shard phải gánh vác toàn bộ công việc trong khi các shard khác lại rảnh rỗi.

Bước 1: Xác định Hot Shard

Việc mở rộng (scaling out) rất tốn kém, vì vậy hãy kiểm tra phân bổ dữ liệu của bạn trước. Nếu bạn sử dụng partition key như Region_ID và vùng US-EAST-1 của bạn bận rộn gấp mười lần các vùng khác, shard cụ thể đó sẽ bị nghẽn ngay cả khi phần còn lại của stream vẫn trống.

Mở CloudWatch console và đi sâu vào các chỉ số (metrics) cụ thể sau:

  • IncomingBytesIncomingRecords (lọc theo ShardId).
  • WriteProvisionedThroughputExceeded.

Hãy tìm các giá trị ngoại lai. Nếu một shard đạt đỉnh 1.000 bản ghi/giây trong khi các shard khác chỉ ở mức 50, bạn đang gặp vấn đề về partition key chứ không phải vấn đề về dung lượng.

Bước 2: Thiết kế lại Partition Key

Tính đa dạng (High cardinality) là yếu tố then chốt ở đây. Bạn cần các key có hàng nghìn giá trị duy nhất để đảm bảo dữ liệu được rải đều trên tất cả các shard hiện có. Thay vì sử dụng một danh mục rộng như Store_Location, hãy thử dùng UUID hoặc một composite key như User_ID + Timestamp.

Nếu bạn cần xác minh cách các key sẽ phân bổ, hashing là một lối tắt đáng tin cậy. Ví dụ: tạo mã hash SHA-256 cho dữ liệu của bạn đảm bảo sự phân bổ ngẫu nhiên gần như hoàn hảo. Bạn có thể sử dụng Hash Generator tại ToolCraft để kiểm tra cách các đầu vào khác nhau tạo ra các chuỗi duy nhất cần thiết cho việc cân bằng các shard.

Bước 3: Thêm Exponential Backoff và Retries

Lưu lượng truy cập hiếm khi là một đường thẳng; nó thường đến theo từng đợt (burst). Ứng dụng của bạn phải đủ linh hoạt để tạm dừng và thử lại khi gặp phải rào cản tạm thời. Mặc dù AWS SDK xử lý một số lần thử lại, việc viết một vòng lặp tùy chỉnh giúp bạn kiểm soát tốt hơn đối với "jitter" và độ trễ.

Dưới đây là một ví dụ Python sử dụng boto3 để triển khai chiến lược backoff cơ bản:

import boto3
import time
import random
from botocore.exceptions import ClientError

kinesis = boto3.client('kinesis', region_name='us-east-1')

def put_record_with_retry(data, partition_key):
    attempt = 0
    max_attempts = 5

    while attempt < max_attempts:
        try:
            return kinesis.put_record(
                StreamName='my-data-stream',
                Data=data,
                PartitionKey=partition_key
            )
        except ClientError as e:
            if e.response['Error']['Code'] == 'ProvisionedThroughputExceededException':
                attempt += 1
                # Exponential backoff với một chút jitter ngẫu nhiên
                delay = (2 ** attempt) + random.uniform(0, 1)
                print(f"Throttled. Retry {attempt}/{max_attempts} in {delay:.2f}s...")
                time.sleep(delay)
            else:
                raise e
    raise Exception("Failed after max retries")

Bước 4: Mở rộng Stream (Resharding)

Đôi khi bạn chỉ đơn giản là cần một đường ống lớn hơn. Nếu CloudWatch cho thấy tất cả các shard của bạn liên tục duy trì ở mức gần 800-900 bản ghi mỗi giây, đã đến lúc phải reshard. Quá trình này giúp tăng dung lượng mà không làm stream của bạn bị ngừng hoạt động (offline).

Sử dụng AWS CLI để tăng số lượng shard của bạn ngay lập tức:

aws kinesis update-shard-count \
    --stream-name my-data-stream \
    --target-shard-count 10 \
    --scaling-type UNIFORM_SCALING

Hãy nhớ rằng việc tăng gấp đôi số lượng shard sẽ làm tăng gấp đôi chi phí hàng giờ của bạn. Hãy theo dõi mức độ sử dụng để đảm bảo bạn không cung cấp dư thừa (over-provisioning).

Cách xác minh việc khắc phục

Đừng chỉ rời đi sau khi mã nguồn đã được triển khai. Hãy theo dõi ba khu vực này để đảm bảo các lỗi đã biến mất:

  • CloudWatch Trends: Chỉ số WriteProvisionedThroughputExceeded nên giảm xuống gần bằng không.
  • Consumer Health: Kiểm tra độ trễ (lag) của Lambda hoặc KCL. Nếu các producer thử lại quá thường xuyên, các consumer của bạn có thể hết dữ liệu để xử lý.
  • Error Logs: Chạy một lệnh grep nhanh hoặc truy vấn Insight trên log của bạn. Lỗi ProvisionedThroughputExceededException chỉ nên xuất hiện trong các đợt tăng vọt lưu lượng lớn và bất ngờ.

Mẹo từ chuyên gia

  • Batch your writes: Đừng sử dụng put_record nữa. Hãy sử dụng put_records (số nhiều) để gửi tối đa 500 bản ghi trong một yêu cầu. Điều này giúp cắt giảm đáng kể chi phí HTTP (overhead).
  • Try the Kinesis Producer Library (KPL): Đối với người dùng Java, KPL là một công cụ mạnh mẽ. Nó xử lý việc gom nhóm (batching) và thử lại phức tạp ở chế độ nền, giúp producer của bạn hiệu quả hơn nhiều.
  • Consider On-Demand Mode: Nếu lưu lượng truy cập của bạn biến động thất thường và bạn ghét việc phải mở rộng thủ công, hãy chuyển sang chế độ On-Demand. Bạn sẽ trả phí cao hơn cho mỗi GB, nhưng AWS sẽ tự động quản lý shard cho bạn.

Related Error Notes