Как максимизировать WRU в DynamoDB?

Мне нужно еженедельно массово вставлять записи в базу данных DynamoDB. Я делаю это, удаляя таблицу, создавая новую таблицу с емкостью On Demand, а затем используя BatchWriteItem для заполнения таблицы. Согласно документации, вновь созданные таблицы с включенным Спросная мощность может обслуживать до 4000 WCU. Как бы я ни старался, максимум, что я могу получить, это 1487 WCU. Я пробовал следующее:

  • Случайный порядок записей перед записью, чтобы избежать горячих разделов
  • Разделение записей на группы и параллельная запись каждой группы в свою лямбда-функцию
  • Запуск процесса в разных средах, включая EC2, Lambda и локально (локально медленнее, предположительно из-за задержки)
  • Изменение типа емкости с On Demand на Provisioned с 5 RCU и 10 000 WCU
  • Использование async/await различными способами, чтобы попытаться максимизировать пропускную способность (я использую AWS SDK для .NET)

Хотя пропускная способность отличается от эксперимента к эксперименту и от выполнения к выполнению, 1487 WCU появляются достаточно часто, чтобы иметь какое-то значение.

Что мне нужно сделать, чтобы использовать все 4000 доступных мне WCU?


person Teppic    schedule 26.05.2021    source источник
comment
Вы действительно получаете ProvisionedThroughputExceeded исключений или что-то в этом роде? В противном случае ограничением является инструмент, который записывает в таблицу, а не сама служба. Здесь общий подход будет заключаться в увеличении количества потоков/процессов и распараллеливания в целом.   -  person Maurice    schedule 26.05.2021
comment
Привет @Maurice, я не получаю исключение ProvisionedThroughputExceeded, однако иногда получаю необработанные элементы в ответе. Я использую экспоненциальную отсрочку для повторной обработки этих элементов. Я попытался увеличить распараллеливание, распределив запись по нескольким лямбда-функциям. Считаете ли вы подход правильным?   -  person Teppic    schedule 26.05.2021
comment
В целом такой подход кажется верным, но его работоспособность во многом зависит от реализации. Я ожидаю, что вы сможете насытить 1000 WRU с одной машины многопоточностью, используя достаточно мощный сервер, сетевое подключение и C#.   -  person Maurice    schedule 26.05.2021


Ответы (1)


Ваше ограничение, по-видимому, на стороне писателя, я написал небольшой скрипт Python для создания и загрузки тестовой таблицы.

Мы видим, что DynamoDB легко масштабируется до 4000 WRU с 8 рабочими процессами, затем немного замедляется, а затем снова масштабируется. Чтобы получить большую пропускную способность, мне пришлось бы добавить больше процессов записи:

Единицы запросов на запись и события регулирования

Вот скрипт для вашего удобства:

import multiprocessing
import typing
import uuid

import boto3
import boto3.dynamodb.conditions as conditions

from botocore.exceptions import ClientError

TABLE = "speed-measurement"
NUMBER_OF_WORKERS = 8

def create_table_if_not_exists(table_name: str):

    try:
        boto3.client("dynamodb").create_table(
            AttributeDefinitions=[{"AttributeName": "PK", "AttributeType": "S"}],
            TableName=table_name,
            KeySchema=[{"AttributeName": "PK", "KeyType": "HASH"}],
            BillingMode="PAY_PER_REQUEST"
        )
    except ClientError as err:
        if err.response["Error"]["Code"] == 'ResourceInUseException':
            # Table already exists
            pass
        else:
            raise err

def write_fast(worker_num):

    table = boto3.resource("dynamodb").Table(TABLE)

    counter = 0

    with table.batch_writer() as batch:
        while True:

            counter += 1
            
            result = batch.put_item(
                Item={
                    "PK": str(uuid.uuid4())
                }
            )

            if counter % 1000 == 0:
                print(f"Worker: #{worker_num} Wrote item #{counter}")

def main():
    create_table_if_not_exists(TABLE)
    
    with multiprocessing.Pool(NUMBER_OF_WORKERS) as pool:
        pool.map(write_fast, range(NUMBER_OF_WORKERS))

if __name__ == "__main__":
    main()

Просто запустите его с помощью Python 3 и остановите, нажав Ctrl+C, как только увидите нужные показатели. Он создаст таблицу и просто запишет ее так быстро, как только сможет, за 8 процессов. Вы также можете увеличить это число.

Источник графики CloudWatch:

{
    "metrics": [
        [ { "expression": "m2/60", "label": "Write Request Units", "id": "e1", "color": "#2ca02c" } ],
        [ "AWS/DynamoDB", "WriteThrottleEvents", "TableName", "speed-measurement", { "yAxis": "right", "id": "m1" } ],
        [ ".", "ConsumedWriteCapacityUnits", ".", ".", { "stat": "Sum", "period": 1, "id": "m2", "visible": false } ]
    ],
    "view": "timeSeries",
    "stacked": false,
    "region": "eu-central-1",
    "stat": "Maximum",
    "period": 60,
    "yAxis": {
        "left": {
            "label": "Consumed Write Request Units",
            "showUnits": false
        },
        "right": {
            "label": "Write Throttle Events",
            "showUnits": false
        }
    },
    "annotations": {
        "horizontal": [
            {
                "color": "#9edae5",
                "label": "Initial Limit",
                "value": 4000,
                "fill": "below"
            }
        ]
    },
    "legend": {
        "position": "bottom"
    },
    "setPeriodToTimeRange": true
}
person Maurice    schedule 26.05.2021
comment
Это фантастика, Морис, большое спасибо. Позвольте мне настроить это в моей среде, и я отчитаюсь. - person Teppic; 26.05.2021
comment
:) - Я также добавил источник для метрик CloudWatch, чтобы вы могли легко создать график. - person Maurice; 26.05.2021
comment
О нет, я только что понял, что я делаю неправильно; мой набор данных был недостаточно большим, чтобы в полной мере использовать доступные WCU! Ваш график - это то, что подсказало мне; Я заметил, что он работал около 10-15 минут, тогда как мои тесты завершались примерно за 30 секунд. Я увеличил размер своего набора данных и смог максимально использовать WCU. Спасибо за помощь, Морис :) - person Teppic; 26.05.2021