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

Amazon DynamoDB — это полностью управляемый сервис NoSQL от AWS, спроектированный для приложений, которым нужны стабильно низкие задержки чтения и записи практически при любом масштабе. «Полностью управляемый» означает, что AWS берет на себя инфраструктурную работу — снабжение железа, репликацию, патчи и множество операционных задач — чтобы команды могли фокусироваться на фичах, а не на серверах баз данных.
В основе DynamoDB данные хранятся как items (записи) в таблицах, но каждый item может иметь гибкие атрибуты. Модель данных удобнее всего представляется как сочетание:
Команды выбирают DynamoDB, когда им нужны предсказуемые показатели производительности и проще управление для рабочих нагрузок, которые плохо ложатся на реляционные JOIN’ы. Частые случаи использования — микросервисы (каждый сервис владеет своими данными), безсерверные приложения со всплесками трафика и event-driven системы, реагирующие на изменения данных.
В этой статье рассмотрим базовые блоки (таблицы, ключи и индексы), как моделировать данные под шаблоны доступа (включая single-table design), как работают масштабирование и режимы ёмкости, а также практические шаблоны для стриминга изменений в event-driven архитектуру.
DynamoDB организована вокруг нескольких простых блоков, но детали важны, потому что они определяют, как моделируются данные и насколько быстро (и экономично) выполняются запросы.
Таблица — это контейнер верхнего уровня. Каждая запись в таблице — item (похожая на строку), и каждый item состоит из набора атрибутов (аналог колонок).
В отличие от реляционных баз данных, items в одной таблице не обязаны иметь одинаковые атрибуты. Один item может содержать {status, total, customerId}, а другой — {status, shipmentTracking} — DynamoDB не требует фиксированной схемы.
Каждый item уникально идентифицируется первичным ключом, и DynamoDB поддерживает два типа:
На практике составные ключи позволяют «группировать» доступы, например «все заказы клиента, от самых новых».
Query считывает items по первичному ключу (или ключу индекса). Он нацелен на конкретный partition key и может фильтроваться по диапазонам sort key — это эффективный, предпочитаемый путь.
Scan просматривает всю таблицу (или индекс) и затем фильтрует. Это просто начать, но при масштабе обычно медленнее и дороже.
Несколько ограничений, с которыми вы столкнётесь рано:
Эти основы задают всё остальное: шаблоны доступа, выбор индексов и характеристики производительности.
DynamoDB часто называют одновременно key-value хранилищем и документной базой. Это верно, но полезно понимать, что каждое из описаний значит для повседневного проектирования.
В основе вы получаете данные по ключу. Укажите значения первичного ключа, и DynamoDB вернёт один item. Такой доступ по ключу даёт предсказуемую, низкозадержанную работу для многих нагрузок.
В то же время item может содержать вложенные атрибуты (maps и lists), что делает его похожим на документную базу: можно хранить структурированные полезные нагрузки без жёсткой схемы заранее.
Items естественно отображают JSON-подобные данные:
profile.name, profile.address).Это подходит, когда сущность обычно читается целиком — профиль пользователя, корзина покупок или конфигурационный пакет.
DynamoDB не поддерживает серверные JOIN’ы. Если приложению нужно получить «заказ + позиции заказа + статус доставки» за один путь чтения, часто приходится денормализовать: скопировать некоторые атрибуты в несколько items или встраивать небольшие подструктуры прямо в item.
Денормализация увеличивает сложность записи и может вести к fan-out при обновлениях. Зато выигрываешь в меньшем числе сетевых запросов и более быстрых чтениях — часто это критично для масштабируемых систем.
Самые быстрые запросы в DynamoDB — это те, которые можно выразить как «верни эту партицию» (и опционально «в этой партиции верни этот диапазон»). Поэтому выбор ключа в основном диктуется тем, как вы читаете данные, а не только тем, как их храните.
Partition key определяет, в каком физическом разделе хранится item. DynamoDB хеширует это значение, чтобы распределить данные и трафик. Если много запросов концентрируются на небольшом наборе значений partition key, вы получите «горячие» партиции и попадёте в лимиты пропускной способности, даже если таблица в целом простаивает.
Хорошие partition key:
"GLOBAL")С sort key items с одинаковым partition key хранятся вместе и упорядочены по sort key. Это даёт эффективные возможности для:
BETWEEN, begins_with)Обычная практика — комбинированный sort key, например TYPE#id или TS#2025-12-22T10:00:00Z, чтобы поддерживать несколько форм запросов без дополнительных таблиц.
PK = USER#<id> (простая GetItem)PK = USER#<id>, SK begins_with ORDER# (или SK = CREATED_AT#...)PK = DEVICE#<id>, SK = TS#<timestamp> с BETWEEN для временных оконЕсли ваш partition key совпадает с самыми горячими запросами и равномерно распределяет нагрузку, вы получите стабильные низкие задержки. Если нет, вам придётся компенсировать сканами, фильтрами или дополнительными индексами — каждый из этих вариантов увеличивает стоимость и риск горячих ключей.
Вторичные индексы дают DynamoDB альтернативные пути доступа к данным кроме первичного ключа таблицы. Вместо того чтобы перестраивать базовую таблицу под новые запросы, вы можете добавить индекс, который переключит ключи для других запросов.
Global Secondary Index (GSI) имеет собственный partition key (и опционально sort key), который может полностью отличаться от таблицы. Он «глобальный», потому что охватывает все партиции таблицы и может быть добавлен или удалён в любое время. Используйте GSI, когда нужен новый шаблон доступа, не укладывающийся в исходную модель ключей — например, поиск заказов по customerId, когда таблица индексирована по orderId.
Local Secondary Index (LSI) использует тот же partition key, что и основная таблица, но другой sort key. LSI нужно задать при создании таблицы. Они полезны, когда вы хотите несколько порядков сортировки внутри одной группы сущностей (того же partition key), например, получение заказов клиента, отсортированных по createdAt или по status.
Проекция определяет, какие атрибуты DynamoDB хранит в индексе:
Каждая запись в базовой таблице может инициировать записи в одном или нескольких индексах. Больше GSIs и широкие проекции увеличивают стоимость записей и потребление ёмкости. Планируйте индексы исходя из стабильных шаблонов доступа и минимизируйте проецируемые атрибуты, когда это возможно.
Масштабирование DynamoDB начинается с выбора: On-Demand или Provisioned ёмкости. Оба достигают высокой пропускной способности, но ведут себя по-разному при изменении трафика.
On-Demand проще: вы платите за запросы, и DynamoDB автоматически обрабатывает переменную нагрузку. Хороший выбор для непредсказуемого трафика, ранних стадий продукта и всплесков, когда вы не хотите управлять целями ёмкости.
Provisioned — это планирование ёмкости: вы указываете (или авто-масштабируете) пропускную способность чтений и записей и получаете более предсказуемые расходы при стабильном использовании. Часто дешевле для предсказуемых нагрузок.
Provisioned throughput измеряется в:
Размер items и шаблоны доступа определяют реальную стоимость: большие items, строгая согласованность и сканы быстро «сжигают» ёмкость.
Auto scaling корректирует provisioned RCUs/WCUs в зависимости от целевого использования. Он помогает при постепенном росте и предсказуемых циклах, но не мгновенный. Внезапные всплески всё ещё могут привести к троттлингу, и авто-масшалирование не решит проблему горячих партиций.
DynamoDB Accelerator (DAX) — это кеш в памяти, который снижает задержку чтений и разгружает повторяющиеся запросы (например, популярные страницы товаров, сессии, таблицы лидеров). Полезен, когда многие клиенты часто запрашивают одни и те же items; не помогает для интенсивных записей и не заменяет продуманную схему ключей.
DynamoDB позволяет обменивать гарантии согласованности на задержку и стоимость, поэтому важно чётко понимать, что означает «корректно» для каждой операции.
По умолчанию GetItem и Query используют eventually consistent чтения: вы можете кратковременно увидеть старое значение сразу после записи. Это часто приемлемо для лент, каталогов товаров и других представлений с преобладающим чтением.
При strongly consistent чтениях (опция для чтений из базовой таблицы в одном регионе) DynamoDB гарантирует, что вы увидите последнее подтверждённое обновление. Strong consistency требует больше RCUs и может увеличить хвостовую задержку, поэтому применяйте её для действительно критичных случаев.
Сильная консистентность важна для операций, которые контролируют необратимые действия:
Для счётчиков чаще безопаснее использовать атомарное обновление (например, UpdateItem с ADD), чтобы инкременты не терялись.
Транзакции (TransactWriteItems, TransactGetItems) обеспечивают ACID-семантику для до 25 items. Полезны, когда нужно обновлять несколько элементов вместе — например, записать заказ и зарезервировать инвентарь — или поддерживать инварианты, которые не переносят промежуточных состояний.
Повторы — нормальная часть распределённых систем. Делайте записи идемпотентными, чтобы повторы не дублировали эффекты:
ConditionExpression (например, attribute_not_exists при создании)Корректность в DynamoDB — это в основном выбор нужного уровня согласованности и проектирование операций так, чтобы повторы не ломали данные.
DynamoDB хранит данные таблицы по физическим партициям. У каждой партиции ограниченная пропускная способность для чтений и записей, а также лимит по объёму данных. Ваш partition key определяет, где находится item; если слишком много запросов на одно значение partition key (или небольшой набор), эта партиция становится узким местом.
Горячие партиции обычно возникают из-за выбора ключей, концентрирующих трафик: «глобальные» ключи вроде USER#1, TENANT#default или STATUS#OPEN, либо шаблоны по времени, где все пишут в «сейчас» под одним ключом.
Вы обычно увидите:
ProvisionedThroughputExceededException) для части ключейПроектируйте для равномерного распределения в первую очередь, затем — для удобства запросов:
TENANT#<id>, а не общая константа)ORDER#<id>#<shard>, чтобы распределять записи по N шардам и затем выполнять запросы по всем шардам при необходимостиMETRIC#2025-12-22T10), чтобы избежать «все записи идут в текущий элемент»Для непредсказуемых пиков on-demand может поглотить всплески (в рамках сервисных лимитов). При provisioned используйте авто-масштабирование и реализуйте на клиенте exponential backoff с jitter при троттлинге, чтобы избежать синхронных повторов, усугубляющих всплеск.
Моделирование данных в DynamoDB начинается с шаблонов доступа, а не с ER-диаграмм. Вы проектируете ключи так, чтобы нужные запросы становились быстрыми Query операциями, а всё остальное либо избегалось, либо обрабатывалось асинхронно.
«Single-table design» означает хранение нескольких типов сущностей (пользователи, заказы, сообщения) в одной таблице и использование согласованных конвенций ключей, чтобы получить связанные данные одним Query. Это уменьшает количество перекрёстных вызовов между сущностями и делает задержки предсказуемыми.
Обычный подход — составные ключи:
PK группирует логическую партицию (например, USER#123)SK упорядочивает элементы в этой группе (например, PROFILE, ORDER#2025-12-01, MSG#000123)Это позволяет получить «всё для пользователя» или «только заказы пользователя», выбрав префикс sort key.
Для графоподобных связей хорошо подходит adjacency list: храните рёбра как items.
PK = USER#123, SK = FOLLOWS#USER#456Чтобы поддержать обратные запросы или many-to-many, добавьте перевёрнутое ребро или проецируйте в GSI в зависимости от путей чтения.
Для событий и метрик избегайте неограниченных партиций, разделяя по бакетам:
PK = DEVICE#9#2025-12-22 (устройство + день)SK = TS#1734825600 (временная метка)Используйте TTL для автоматического удаления старых точек и храните агрегаты (почасовые/ежедневные) как отдельные items для быстрых дашбордов.
Если нужно освежить конвенции ключей, посмотрите /blog/partition-key-and-sort-key-design.
DynamoDB Streams — это встроенный механизм CDC (change data capture). Когда Streams включены для таблицы, каждая вставка, обновление или удаление порождает запись потока, на которую downstream-потребители могут реагировать — без опроса таблицы.
Запись стрима содержит ключи и (опционально) старое и/или новое изображение item, в зависимости от выбранного stream view type (только ключи, new image, old image, both). Записи группируются в shards, которые читаются последовательно.
Обычная схема: DynamoDB Streams → AWS Lambda, где каждая партия записей триггерит функцию. Возможны и другие потребители (кастомные потребители или отправка в аналитические/логирующие системы).
Типичные рабочие сценарии:
Так вы держите основную таблицу оптимизированной для быстрых чтений/записей и выносите производную работу в асинхронных потребителей.
Streams гарантируют порядок обработки в пределах шарда (что обычно коррелирует с partition key), но глобального порядка нет. Доставка — at-least-once, поэтому возможны дубликаты.
Чтобы это безопасно обрабатывать:
Спроектированные с учётом этих гарантий, Streams превращают DynamoDB в надёжный каркас для event-driven систем.
DynamoDB рассчитан на высокую доступность, распределяя данные по нескольким AZ в регионе. Практическая надёжность достигается благодаря понятной стратегии бэкапов, осознанию опций репликации и мониторингу ключевых метрик.
On-demand backups — это ручные (или автоматизированные) снимки, которые вы делаете в ключевые моменты — перед миграцией, после релиза или перед массированными изменениями. Хороши для «закладок» состояния.
Point-in-time recovery (PITR) постоянно захватывает изменения, позволяя восстановить таблицу к любому моменту в пределах окна ретеншна. PITR — страховка от случайных удалений и ошибочных записей.
Если нужна мульти-региональная устойчивость или низкая задержка чтений рядом с пользователями, Global Tables реплицируют данные в выбранных регионах. Они упрощают планирование фейловера, но добавляют задержки репликации и вопросы разрешения конфликтов — держите паттерны записи и владение items понятными.
Минимум — алерты по:
Эти сигналы обычно указывают на проблему с горячими партициями, недостаточной ёмкостью или неожиданными шаблонами доступа.
При троттлинге сначала найдите шаблон доступа, его вызывающий, затем смягчите временно, переключившись на on-demand или увеличив provisioned ёмкость, и рассмотрите шардирование горячих ключей.
При частичных сбоях или росте ошибок уменьшите радиус поражения: отключите некритичный трафик, примените повторы с jitter и деградируйте gracefully (например, отдавая кэшированные чтения), пока таблица не стабилизируется.
Безопасность в DynamoDB — это в основном ограничение того, кто и какие API-операции может выполнять, откуда и по каким ключам. Поскольку таблицы могут содержать разные типы сущностей (и иногда несколько арендаторов), контроль доступа стоит проектировать вместе с моделью данных.
Начните с identity-based IAM политик, ограничивающих действия (например, dynamodb:GetItem, Query, PutItem) до минимально необходимых и сужайте их до конкретных ARN таблиц.
Для более тонкого контроля используйте dynamodb:LeadingKeys, чтобы ограничивать доступ по значениям partition key — полезно, когда сервис или арендатор должен работать только в своём пространстве ключей.
DynamoDB шифрует данные «на покое» по умолчанию с AWS-owned ключами или с KMS-ключом, управляемым клиентом. Если у вас есть требования соответствия, убедитесь:
Для шифрования «в пути» убедитесь, что клиенты используют HTTPS (AWS SDK делает это по умолчанию). Если TLS завершается в прокси, проверьте, что канал между прокси и DynamoDB по-прежнему зашифрован.
Используйте VPC Gateway Endpoint для DynamoDB, чтобы трафик оставался в сети AWS и вы могли применять политики endpoints для ограничения доступа. Сочетайте это с контролем исходящего трафика (NACLs, security groups и маршрутизацией), чтобы исключить «всё может уйти в интернет» сценарии.
Для общих таблиц включайте идентификатор арендатора в partition key (например, TENANT#<id>), а затем применяйте изоляцию через IAM-условия по dynamodb:LeadingKeys.
Если нужна более строгая изоляция — рассматривайте отдельные таблицы на арендатора или окружение; shared-table дизайны оставьте там, где простота операций и экономия важнее меньшей blast radius.
DynamoDB часто «дёшев при точном подходе, дорог при расплывчатости». Счёт обычно следует за шаблонами доступа, поэтому оптимизация начинается с их явного описания.
Ваш счёт формируется в основном за счёт:
Распространённый сюрприз: каждая запись в таблицу — это также запись в каждом затронутом GSI, так что «ещё один индекс» может умножить стоимость записей.
Хороший дизайн ключей снижает потребность в дорогостоящих операциях. Если вы часто используете Scan, вы оплачиваете чтение данных, которые затем отбрасываете.
Отдавайте предпочтение:
Query по partition key (и опционально условиям по sort key)Если шаблон доступа редкий, рассмотрите отдельную таблицу, ETL-задачу или материализованную модель чтения вместо постоянного GSI.
Используйте TTL для автоматического удаления краткоживущих items (сессии, временные токены, промежуточное состояние воркфлоу). Это сокращает хранение и помогает держать индексы маленькими.
Для данных с постоянным добавлением (события, логи) комбинируйте TTL с дизайном sort key, который позволяет запрашивать «только последние», чтобы не трогать холодную историю регулярно.
В режиме provisioned задавайте консервативные базовые значения и масштабируйте через авто-масштабирование по реальным метрикам. В on-demand следите за неэффективными паттернами (большие items, чатти-клиенты), которые увеличивают количество запросов.
Относитесь к Scan как к крайнему средству — если нужен полный проход по таблице, выполняйте его в непиковое время или как управляемую batch-операцию с пагинацией и бэкоффом.
DynamoDB сияет, когда приложение можно выразить через набор хорошо определённых шаблонов доступа и нужна стабильная низкая задержка при большом масштабе. Если вы заранее можете описать чтения и записи (по partition key, sort key и небольшому числу индексов), это часто один из самых простых путей к управляемому высокодоступному хранилищу.
DynamoDB хороша, когда у вас:
Ищите другие решения, если в основе лежит:
Многие команды оставляют DynamoDB для «горячих» операционных чтений и записей, а затем добавляют:
Если вы валидируете шаблоны доступа и конвенции single-table, скорость имеет значение. Команды иногда прототипируют сервис и UI в Koder.ai (платформа vibe-coding, строящая веб, сервер и мобильные приложения из чата), а затем итерационно меняют дизайн ключей по мере появления реальных путей запросов. Быстрые end-to-end прототипы помогают понять, какие запросы должны быть Query, а какие случайно превратятся в дорогие Scan.
Проверьте: (1) ваши основные запросы известны и основаны на ключах, (2) требования по корректности соответствуют модели согласованности, (3) понятны ожидаемые размеры items и рост, и (4) модель затрат (on-demand vs provisioned + autoscaling) укладывается в бюджет.
DynamoDB — это полностью управляемая NoSQL-база данных от AWS, предназначенная для обеспечения постоянно низкой задержки чтений и записей при очень больших объёмах. Команды выбирают её, когда могут задать доступ по ключам (получение по ID, список по владельцу, диапазоны по времени) и хотят не заниматься обслуживанием серверов баз данных.
Особенно распространён в микросервисах, безсерверных приложениях и event-driven архитектурах.
Таблица хранит items (записи, похожие на строки). Каждый item — это гибкий набор атрибутов (как колонки) и может содержать вложенные структуры.
DynamoDB удобна, когда один запрос обычно возвращает «всю сущность», поскольку item может включать maps и lists (структуры, похожие на JSON).
Один атрибут partition key сам по себе уникально идентифицирует item (простое первичное ключирование). Комбинация partition key + sort key (составной ключ) позволяет нескольким элементам разделять один и тот же partition key, оставаясь уникальными и упорядоченными по sort key.
Составные ключи дают шаблоны вроде:
Используйте Query, когда вы можете указать partition key (и при необходимости условие по sort key). Это быстрый и масштабируемый путь.
Используйте Scan только если действительно нужно прочитать всё; он проходит по всей таблице или индексу и фильтрует уже после чтения — обычно медленнее и дороже.
Если вы часто используете Scan, это признак того, что дизайн ключей или индексов стоит пересмотреть.
Вторичные индексы дают альтернативные пути запросов.
partition key (и опционально sort key), отличный от таблицы; индекс можно добавить позже.Выберите On-Demand, если трафик непредсказуемый или всплески, и вы не хотите управлять ёмкостью. Плата — за запросы.
Выберите Provisioned, если нагрузка стабильна/предсказуема и вы хотите более контролируемые расходы. Комбинируйте с авто-масштабированием, но учтите, что оно не мгновенно реагирует на резкие всплески.
По умолчанию чтения — eventually consistent, т.е. после записи вы можете кратковременно увидеть устаревшее значение.
Используйте strongly consistent чтения (когда доступны) для критичных проверок, например, для контроля авторизации или важных шагов рабочего процесса.
Для корректности при конкурентных операциях чаще применяйте атомарные обновления (например, UpdateItem с ADD) вместо схемы «прочитать-изменить-записать».
Транзакции (TransactWriteItems, TransactGetItems) дают ACID-гарантии для до 25 items.
Используйте их, когда нужно одновременно обновить несколько элементов (например, создать заказ и зарезервировать инвентарь) или обеспечить инварианты, не допускающие промежуточных состояний.
Они дороже и увеличивают задержку, поэтому применяйте только там, где это действительно необходимо.
Горячие партиции возникают, когда слишком много запросов направлено на одно значение partition key (или небольшой набор значений), что ведёт к троттлингу, даже если таблица в целом не загружена.
Типичные способы смягчения:
Включите DynamoDB Streams, чтобы получать поток изменений при вставках, обновлениях и удалениях. Типичный паттерн: Streams → Lambda для выполнения побочных задач.
Ключевые гарантии, о которых нужно помнить:
Делайте обработчики (upsert по ключу, условные записи или храните ID обработанных событий).
partition key, что и основная таблица, но другой sort key; определяется при создании таблицы.Индексы увеличивают стоимость записи, потому что запись дублируется в индексах.
partition key с высокой кардинальностью