Amazon DynamoDB — NoSQL база данных в облаке
Amazon DynamoDB — полностью управляемая NoSQL база данных от AWS. Она сама масштабируется, реплицируется и отвечает миллисекундами на запросы, поэтому её выбирают для высоконагруженных веб- и мобильных приложений.
1. Что такое DynamoDB и NoSQL
DynamoDB — это key-value и документная база данных, запущенная AWS в 2012 году. В отличие от реляционных СУБД, она не требует фиксированной схемы: каждый элемент может хранить свой набор атрибутов.
- Полностью управляемая — не нужно ставить сервер, настраивать кластер или делать бэкапы.
- Серверная (serverless) — платишь только за потреблённые ресурсы.
- Миллисекундная задержка — стабильная латентность даже при миллионах запросов.
- Мультирегиональность — репликация между несколькими регионами AWS.
Когда выбирать 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 key | user_id |
| Составной ключ | partition key + sort key | user_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"
}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}
}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"} }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": "Москва"} }| Критерий | Query | Scan |
|---|---|---|
| Стоимость | читает только нужные партиции | читает всю таблицу |
| Обязателен partition key | да | нет |
| Скорость | высокая | низкая на больших таблицах |
| Когда применять | основной доступ к данным | одноразовые выгрузки, экспорт |
7. Вторичные индексы: GSI и LSI
Если нужно искать по атрибуту, который не входит в первичный ключ, используют вторичные индексы. DynamoDB автоматически поддерживает их в актуальном состоянии.
- GSI (Global Secondary Index) — индекс с собственным ключом, строится над всей таблицей. Создаётся в любой момент.
- LSI (Local Secondary Index) — индекс с тем же partition key и другим sort key. Создаётся только при создании таблицы.
-- создание GSI "UsersByCity"
-- partition key: city
// AttributeDefinitions: city (S)
// GlobalSecondaryIndexes:
// UsersByCity: { city (HASH) }
// ProjectionType: ALL8. Автомасштабирование и ёмкость
Производительность 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
}
}ThrottlingException. AWS Application Auto Scaling автоматически повышает лимиты при росте нагрузки, но повышения происходят не мгновенно.9. Модель оплаты: on-demand и provisioned
DynamoDB поддерживает два режима биллинга:
| Режим | Как работает оплата | Когда подходит |
|---|---|---|
| PAY_PER_REQUEST (on-demand) | платишь за каждое фактическое чтение и запись | непредсказуемый трафик, стартапы |
| PROVISIONED | платишь за зарезервированную ёмкость | стабильная нагрузка, экономия на масштабе |
Scan гигантской таблицы в on-demand режиме может ощутимо увеличить счёт.10. DynamoDB из Python (boto3)
Официальный SDK для Python — boto3. Установи пакет и сконфигурируй учётные данные AWS:
pip install boto3
aws configure # введи Access Key и Secret Keyimport 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},
)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"])- поток включается на уровне таблицы;
- сообщения хранятся 24 часа;
- потребители: Lambda, EventBridge, трансформация из Kinesis.
12. Глобальные таблицы
Глобальные таблицы (DynamoDB Global Tables) — мультирегиональная репликация для низкой задержки и переживания сбоев региона:
- таблица активна сразу в нескольких регионах;
- запись в любом регионе синхронизируется в остальные;
- чтение обычно идёт из ближайшего региона;
- репликация автоматическая и почти в реальном времени.
import boto3
client = boto3.client("dynamodb")
client.update_table(
TableName="users",
ReplicaUpdates=[{"Create": {"RegionName": "eu-central-1"}}],
)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- автоматические backups не отключаются;
- PITR восстанавливает в новую таблицу (media/имя), прошлое не перезаписывается;
- восстанавливай регулярно и проверяй целостность данных.
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"),
)- группируй по шаблону ENTITY#ID;
- по каждому сценарию доступа проектируй GSI (раздел 7);
- жертвуй «чистой» нормализацией ради скорости чтения.
15. Транзакции и атомарные операции
Когда несколько записей должны измениться вместе, используй транзакции (до 100 элементов, до 4 МБ):
# атомарный инкремент без транзакции
table.update_item(
Key={"PK": "ORDER#o1"},
UpdateExpression="SET total = total + :d",
ExpressionAttributeValues={":d": Decimal("99.90")},
)TransactWriteItems— атомарный набор insert/update/delete/condition: либо всё фиксируется, либо ничего;TransactGetItems— согласованное чтение нескольких ключей;- ConditionExpression — «вставь только если атрибута нет» (защита от затирания);
- для одного атрибута дешевле атомарные инкременты
ADD/UpdateExpression.
table.transact_write(TransactItems=[
{"Put": {"Key": {"PK": {"S": "ORDER#o1"}}, ...}},
{"Update": {"Key": {"PK": {"S": "USER#u1"}}, "UpdateExpression": "SET total = total + :d", ...}},
])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- экспорт не тратит read-capacity и не блокирует запись;
- данные попадают в
Parquet/JSONв S3 — читаются Athena, DuckDB, Spark; - для периодичности —
EventBridgeпо расписанию.
# 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"
}
}billing_mode = "PAY_PER_REQUEST"— оплата по нагрузке без провижина;- автоскейлинг:
aws_appautoscaling_policy— min/max capacity; - всё ревьюится в PR, восстанавливается из кода — никаких «заклинаний из консоли».
terraform plan
terraform apply18. Типовые паттерны решений
Пара проверенных подходов на DynamoDB:
| Задача | Решение |
|---|---|
| лидерборд | инкремент ADD + сортировка по SK/DESC + глобальный индекс |
| счётчик без потерь | один ключ + обновление атомарно |
| сессии с TTL | атрибут ttl — DynamoDB сама удалит по истечении |
| горячий ключ | шейдинг ключа (user_id + случайный суффикс) для записи |
# TTL включается на консоли/в Terraform
ttl {
enabled = true
attribute_name = "ttl"
}- если пишут в один ключ слишком часто — добавляй шарды и агрегируй при чтении;
- глобальные индексы читают каждый max по своей стоимости — не плоди без нужды.
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")- DAX кэширует в памяти узлов кластера, раздавая до «микромиллисекунд»;
- strongly consistent read обходит кэш — учитывай в архитектуре;
- не кэшируй то, что меняется каждую секунду (счётчики, текущие цены).
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 ...- on-demand и autoscaling гасят пики при резких скачках;
- повторные ошибки на запись — индикатор «горячего» partition;
- AccessDenied рядом с троттлингом — проблема прав, а не ёмкости.
21. IAM: минимум прав для таблицы
Приложению не нужны права администратора DynamoDB:
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query",
"dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:...:table/users"- ограничь по ключам через Condition (например,
dynamodb:LeadingKeysдля tenant'ов); - раздели роли: сервисная (своя таблица) и аналитическая (read-only);
- секреты — в Secrets Manager, ключи ротируй.
22. Кейс: переезд из реляционной БД
Миграция RDBMS → DynamoDB — это переписывание модели данных, а не «замена СУБД»:
- один PK+SK на сущность (single-table) и индексы под все сценарии запросов;
- денормализация: вложенные списки заказов вместо JOIN;
- двусторонняя запись: параллельно пишем в старую и новую БД, сверяем выборочно;
- переключение через 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)Что дальше
Ты разобрался с моделью DynamoDB: ключами, атрибутами, API, индексами и работой из Python. Рекомендуемый следующий шаг — изучить документную NoSQL базу MongoDB, чтобы сравнить подходы, и понять, как DynamoDB встраивается в облачную архитектуру в уроках FastAPI и Docker. Основы моделирования данных NoSQL разобраны в базовом курсе SQL.