Amazon DynamoDB — NoSQL база данных в облаке

Amazon DynamoDB — полностью управляемая NoSQL база данных от AWS. Она сама масштабируется, реплицируется и отвечает миллисекундами на запросы, поэтому её выбирают для высоконагруженных веб- и мобильных приложений.

Уровень: средний 18 разделов ⏱ 3–4 часа

1. Что такое DynamoDB и NoSQL

DynamoDB — это key-value и документная база данных, запущенная AWS в 2012 году. В отличие от реляционных СУБД, она не требует фиксированной схемы: каждый элемент может хранить свой набор атрибутов.

Когда выбирать DynamoDB

СценарийПодходит?
Каталог товаров, сессии, корзиныда, идеально
Логи и события в реальном временида, при потоковой записи
Сложные аналитические запросы с JOINнет, лучше реляционная БД
Транзакции между десятками таблицчастично, транзакции ограничены 100 элементами

2. Основные понятия: таблицы, элементы, атрибуты

Модель данных DynamoDB проще реляционной:

Ключевое отличие от SQL: атрибуты объявляются не заранее. Один элемент может иметь поле price, а другой — нет. Недостающие атрибуты просто считаются отсутствующими, а не NULL.

Пример элемента в JSON-представлении — такой же формат использует API DynamoDB:

{
  "user_id": "u-1001",
  "name": "Анна",
  "age": 28,
  "city": "Москва",
  "orders": [
    {"id": "o-1", "sum": 1500},
    {"id": "o-2", "sum": 3200}
  ]
}

3. Ключи: partition key и sort key

Каждый элемент таблицы должен иметь уникальный первичный ключ. Ключ бывает двух типов:

Тип ключаСоставПример
Простой ключтолько partition keyuser_id
Составной ключpartition key + sort keyuser_id + order_id

Partition key (ключ партиции) определяет, на какую физическую партицию попадёт элемент. Sort key (ключ сортировки) упорядочивает элементы внутри партиции.

-- создание таблицы orders
-- partition key: user_id (строка)
-- sort key:      order_id (строка)
{
  "TableName": "orders",
  "KeySchema": [
    {"AttributeName": "user_id", "KeyType": "HASH"},
    {"AttributeName": "order_id", "KeyType": "RANGE"}
  ],
  "BillingMode": "PAY_PER_REQUEST"
}
Partition key — самая важная проектная ошибка новичков. Если выбрать ключ с малым числом уникальных значений (например, «страна»), все данные лягут на несколько партиций и перегрузят их. Выбирай ключ с высокой кардинальностью: user_id, session_id.

4. Типы данных и атрибуты

DynamoDB поддерживает скалярные типы, множества и документные структуры:

СемействоТипы
СкалярныеString (S), Number (N), Binary (B)
МножестваString Set (SS), Number Set (NS), Binary Set (BS)
ДокументныеMap (M), List (L)
СпециальныеBoolean (BOOL), Null (NULL)
{
  "id": {"S": "item-1"},
  "price": {"N": "1900.50"},
  "tags": {"SS": ["electronic", "sale"]},
  "meta": {"M": {"color": {"S": "black"}}},
  "active": {"BOOL": true}
}
Числа в JSON-представлении API передаются как строки — так протокол сохраняет точность больших чисел и десятичных дробей. В SDK для Python числа приходят уже как числа, эту конвертацию библиотека делает сама.

5. API чтения и записи: PutItem, GetItem

С DynamoDB работают через низкоуровневый API по HTTP. Кажется операция — отдельный метод:

ОперацияЧто делает
PutItemвставляет или полностью заменяет элемент
GetItemчитает элемент по ключу
UpdateItemчастично обновляет атрибуты элемента
DeleteItemудаляет элемент по ключу
BatchGetItemчитает до 100 элементов за один вызов
BatchWriteItemзаписывает до 25 элементов за один вызов

Для атомарного увеличения счётчика в DynamoDB есть операция ADD в выражении обновления. Она не требует читать текущее значение:

// UpdateItem: увеличить "views" на 1
// Expression: ADD views :inc
// AttributeValues: { ":inc": {"N": "1"} }
Операции обновления в DynamoDB по умолчанию идемпотентны: повторный PutItem с тем же ключом просто перезапишет элемент. Для «вставь только если нет» используй параметр ConditionExpression с проверкой на отсутствие ключа.

6. Query vs Scan

Query ищет элементы по точному значению partition key (и, при желании, по условию на sort key). Scan просматривает всю таблицу. Это принципиально разные по цене операции:

-- Query: все заказы пользователя u-1001
-- KeyConditionExpression: user_id = :uid
-- AttributeValues: { ":uid": {"S": "u-1001"} }

-- Query с фильтром по sort key
-- KeyConditionExpression: user_id = :uid AND order_id > :oid
-- AttributeValues: { ":uid": {"S": "u-1001"}, ":oid": {"S": "o-100"} }

Scan читает каждый элемент таблицы и потому стоит дорого. Его лучше избегать в продуктовой среде:

-- Scan: все элементы, где city = "Москва"
-- FilterExpression: city = :c
-- AttributeValues: { ":c": {"S": "Москва"} }
КритерийQueryScan
Стоимостьчитает только нужные партициичитает всю таблицу
Обязателен partition keyданет
Скоростьвысокаянизкая на больших таблицах
Когда применятьосновной доступ к даннымодноразовые выгрузки, экспорт

7. Вторичные индексы: GSI и LSI

Если нужно искать по атрибуту, который не входит в первичный ключ, используют вторичные индексы. DynamoDB автоматически поддерживает их в актуальном состоянии.

-- создание GSI "UsersByCity"
-- partition key: city
// AttributeDefinitions: city (S)
// GlobalSecondaryIndexes:
//   UsersByCity: { city (HASH) }
//   ProjectionType: ALL
В GSI может не быть уникальности: несколько элементов с одинаковым ключом индекса допустимы. Проекция индекса определяет, какие атрибуты копируются в индекс: ALL, KEYS_ONLY или INCLUDE (список). Сильнее проекция — больше гибкости, но и выше стоимость хранения.
LSI и GSI нельзя изменить после создания. Ошибиться с проекцией — значит пересоздать таблицу целиком. Продумывай индексы на этапе проектирования, а не когда приложение уже работает.

8. Автомасштабирование и ёмкость

Производительность DynamoDB измеряется в единицах ёмкости:

ЕдиницаЧто покрывает
WCU (Write Capacity Unit)1 запись до 1 КБ в секунду
RCU (Read Capacity Unit)чтение до 4 КБ в секунду (строго согласованное)
RCU eventualдва чтения до 4 КБ в секунду (согласованность в итоге)
-- режим Provisioned: 5 RCU и 5 WCU с автоскейлингом
{
  "TableName": "orders",
  "BillingMode": "PROVISIONED",
  "ProvisionedThroughput": {
    "ReadCapacityUnits": 5,
    "WriteCapacityUnits": 5
  }
}
Если потратить всю provisioned-ёмкость, запросы начнут получать ошибку ThrottlingException. AWS Application Auto Scaling автоматически повышает лимиты при росте нагрузки, но повышения происходят не мгновенно.

9. Модель оплаты: on-demand и provisioned

DynamoDB поддерживает два режима биллинга:

РежимКак работает оплатаКогда подходит
PAY_PER_REQUEST (on-demand)платишь за каждое фактическое чтение и записьнепредсказуемый трафик, стартапы
PROVISIONEDплатишь за зарезервированную ёмкостьстабильная нагрузка, экономия на масштабе
Тарифы DynamoDB также включают стоимость хранения данных (около $0,25 за ГБ в месяц) и плату за восстановление бэкапов. Для обучения действует тариф AWS Free Tier с бесплатными 25 ГБ в месяц.
Перед началом экспериментов включи Budget Alert в консоли AWS. Случайный Scan гигантской таблицы в on-demand режиме может ощутимо увеличить счёт.

10. DynamoDB из Python (boto3)

Официальный SDK для Python — boto3. Установи пакет и сконфигурируй учётные данные AWS:

pip install boto3

aws configure        # введи Access Key и Secret Key
import boto3
from boto3.dynamodb.conditions import Key, Attr

dynamodb = boto3.resource("dynamodb", region_name="eu-west-1")
table = dynamodb.Table("orders")

# запись элемента
table.put_item(Item={
    "user_id": "u-1001",
    "order_id": "o-1",
    "sum": 1500,
    "city": "Москва",
})

# чтение по ключу
resp = table.get_item(Key={"user_id": "u-1001", "order_id": "o-1"})
print(resp.get("Item"))

# query по partition key
resp = table.query(KeyConditionExpression=Key("user_id").eq("u-1001"))
for item in resp["Items"]:
    print(item)

Частичное обновление и атомарный счётчик:

table.update_item(
    Key={"user_id": "u-1001", "order_id": "o-1"},
    UpdateExpression="SET city = :c ADD views :one",
    ExpressionAttributeValues={":c": "Москва", ":one": 1},
)
Для локальной разработки запусти обёртку DynamoDB Local через Docker — она полностью повторяет поведение облачной базы, но работает бесплатно и офлайн:
docker run -d -p 8000:8000 amazon/dynamodb-local

boto3.resource("dynamodb",
    endpoint_url="http://localhost:8000",
    region_name="us-east-1")

11. DynamoDB Streams и Lambda

Streams дают упорядоченный поток изменений таблицы (вставки, обновления, удаления). Событие содержит «до» и «после» записи — это удобно для аудита, кеширования и реактивных пайплайнов:

table.enable_streams = dict(
    StreamViewType="NEW_AND_OLD_IMAGES"
)
def handler(event, context):
    for rec in event["Records"]:
        if rec["eventName"] == "INSERT":
            item = rec["dynamodb"]["NewImage"]
            print(item["user_id"]["S"])
«Хвост» стрима удобен для автономных интеграций: положил заказ — заявка ушла в аналитику и CRM сама.

12. Глобальные таблицы

Глобальные таблицы (DynamoDB Global Tables) — мультирегиональная репликация для низкой задержки и переживания сбоев региона:

import boto3
client = boto3.client("dynamodb")
client.update_table(
    TableName="users",
    ReplicaUpdates=[{"Create": {"RegionName": "eu-central-1"}}],
)
Глобальные таблицы требуют включённых Streams. Конфликты при записи в разные регионы решает последняя запись (last-writer-wins) — учитывай при дизайне ключей.

13. Резервное копирование и PITR

Восстановление после случайного удаления — обязанность любого хранилища:

# точечное восстановление (point-in-time recovery) — до 35 дней назад
boto3.client("dynamodb").update_continuous_backups(
    TableName="users",
    PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
)
# ручной снимок таблицы
aws dynamodb create-backup \
  --table-name users \
  --backup-name manual-2026-09-19
Восстановление нельзя направить в исходную таблицу: строй процесс как «поднять новую, переключить конфиг, удалить старую».

14. Проектирование: single-table design

В классических БД одна сущность — таблица. В DynamoDB принят single-table design: несколько сущностей в одной таблице через составные ключи:

PK            | SK           | атрибуты
USER#u1       | PROFILE      | name, email
USER#u1       | ORDER#o1     | product, total
USER#u1       | ORDER#o2     | product, total

Так один Query получает профиль и заказы пользователя одной операцией:

table.query(
    KeyConditionExpression=Key("PK").eq("USER#u1"),
)
Начни с 2–3 вариантов доступа (профиль по id, заказы по пользователю) и проектируй ключи под них — это и есть проектирование под DynamoDB.

15. Транзакции и атомарные операции

Когда несколько записей должны измениться вместе, используй транзакции (до 100 элементов, до 4 МБ):

# атомарный инкремент без транзакции
table.update_item(
    Key={"PK": "ORDER#o1"},
    UpdateExpression="SET total = total + :d",
    ExpressionAttributeValues={":d": Decimal("99.90")},
)
table.transact_write(TransactItems=[
    {"Put": {"Key": {"PK": {"S": "ORDER#o1"}}, ...}},
    {"Update": {"Key": {"PK": {"S": "USER#u1"}}, "UpdateExpression": "SET total = total + :d", ...}},
])
Транзакции не решают проблемы «согласованности между таблицами» бесконечно: реальная строгая консистентность в DynamoDB ограничена одним partition key.

16. Экспорт в S3 и аналитика

Для аналитики без нагрузки на боевую таблицу используй on-demand экспорт в S3:

aws dynamodb export-table-to-point-in-time \
  --table-name orders --s3-bucket bucket-name \
  --s3-prefix dynamodb-export --export-format DYNAMODB_JSON
# Athena: запрос прямо по экспортированным файлам
SELECT user_id, COUNT(*) FROM "orders_export"
GROUP BY user_id;
Сочетай с PITR: экспорт даёт снапшот точки во времени, а бэкапы на S3 — архив.

17. Инфраструктура как код: Terraform

Таблица и автополя, созданные в консоли, исчезнут при следующем пересоздании окружения. Опиши DynamoDB в Terraform:

resource "aws_dynamodb_table" "orders" {
  name         = "orders"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "PK"

  attribute {
    name = "PK"
    type = "S"
  }
}
terraform plan
terraform apply
Underlying курс Terraform поднимет IaC-навыки: переменные, окружения, remote-state.

18. Типовые паттерны решений

Пара проверенных подходов на DynamoDB:

ЗадачаРешение
лидербординкремент ADD + сортировка по SK/DESC + глобальный индекс
счётчик без потерьодин ключ + обновление атомарно
сессии с TTLатрибут ttl — DynamoDB сама удалит по истечении
горячий ключшейдинг ключа (user_id + случайный суффикс) для записи
# TTL включается на консоли/в Terraform
ttl {
  enabled        = true
  attribute_name = "ttl"
}
Прорабатывай эти паттерны локально через Docker + amazon/dynamodb-local: дёшево и без облака.

19. DAX: кэш перед таблицами

Хит-чтения на горячих ключах ускоряют трафик и снижают стоимость — для этого есть DynamoDB Accelerator (DAX):

# подмена endpoint'а: остальной код не меняется
session = boto3.session.Session()
client = session.client("dynamodb",
    endpoint_url="https://mycluster.dax-cluster.amazonaws.com")
Начни с включённого кэша и меряй P99: если хит-ратио низкий, DAX не нужен вовсе.

20. Наблюдаемость: CloudWatch и троттлинг

Троттлинг — симптом, а не причина. Следи за метриками и реагируй:

МетрикаАлертЧто делать
ThrottledRequests> 0 за 5 минутшейдинг ключа / DAX / autoscale
ConditionalCheckFailedростпроверить логику блокировок
aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB --metric-name ThrottledRequests \
  --dimensions Name=TableName,Value=products \
  --statistics Sum --period 300 --start-time ... --end-time ...
Средняя ёмкость по таблице обманывает: смотри на max по partition, иначе локальный троттлинг прозевается.

21. IAM: минимум прав для таблицы

Приложению не нужны права администратора DynamoDB:

"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query",
           "dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:...:table/users"
Least privilege касается и потоков Lambda/Streams: триггер должен уметь только PutItem в свою таблицу.

22. Кейс: переезд из реляционной БД

Миграция RDBMS → DynamoDB — это переписывание модели данных, а не «замена СУБД»:

  1. один PK+SK на сущность (single-table) и индексы под все сценарии запросов;
  2. денормализация: вложенные списки заказов вместо JOIN;
  3. двусторонняя запись: параллельно пишем в старую и новую БД, сверяем выборочно;
  4. переключение через feature flag и стоп-условие по расхождению ≤ 0.01%.
def migrate(row):
    item = {"PK": f"USER#{row.id}", "SK": "META",
            "name": row.name, "order_count": len(row.orders)}
    table.put_item(Item=item)
Не переноси полномочия «как есть»: полный JOIN-граф в NoSQL — гарантия перегретых RCU и боли в перфе.

Что дальше

Ты разобрался с моделью DynamoDB: ключами, атрибутами, API, индексами и работой из Python. Рекомендуемый следующий шаг — изучить документную NoSQL базу MongoDB, чтобы сравнить подходы, и понять, как DynamoDB встраивается в облачную архитектуру в уроках FastAPI и Docker. Основы моделирования данных NoSQL разобраны в базовом курсе SQL.

← Вернуться к курсам