DynamoDB — это управляемая база данных типа NoSQL от AWS. «Управляемая» значит, что вам не нужно ставить сервер, обновлять его и думать про репликацию: AWS делает это за вас. «NoSQL» значит, что это не привычная таблица со строками и связями, где можно писать произвольные SQL-запросы, а хранилище типа «ключ-значение»: вы кладёте элемент по ключу и забираете его по тому же ключу. Главная особенность DynamoDB — он держит стабильно низкую задержку (единицы миллисекунд) хоть на тысяче записей, хоть на миллиардах.
Но за это приходится платить сменой привычки. В реляционной базе вы сначала проектируете таблицы «правильно» (нормализуете), а запросы пишете потом — какие угодно. В DynamoDB наоборот: сначала вы должны точно знать, какие запросы будете делать, и уже под них выбираете ключи. Если спроектировать DynamoDB «как обычные таблицы», получите либо медленные дорогие переборы всей базы, либо вообще не сможете достать нужные данные. Поэтому всё начинается с модели доступа — со списка вопросов, которые приложение задаёт к данным.
Ключи: partition key и sort key
У каждого элемента (так в DynamoDB называют запись, аналог строки) есть первичный ключ. Он состоит из одной или двух частей.
Partition key (ключ партиции) — обязательная часть. По нему DynamoDB решает, на каком физическом «куске» хранилища (партиции) будет лежать элемент. Представьте библиотеку с множеством стеллажей: partition key — это номер стеллажа. От него зависит, насколько ровно распределится нагрузка. Плохой ключ — например, у которого мало разных значений или один встречается чаще остальных — создаёт «горячую» партицию: один стеллаж осаждён читателями, остальные пустуют, и он становится бутылочным горлышком.
Sort key (ключ сортировки) — необязательная вторая часть. Если partition key — это номер стеллажа, то sort key — это порядок книг на полке. Он упорядочивает элементы внутри одной партиции и позволяет делать запросы по диапазону: «все заказы пользователя за март», «всё, что начинается с ORDER#». Для этого есть операторы вроде begins_with (начинается с) и between (в диапазоне).
Главная операция чтения — Query. Она работает быстро, но только по ключу: «дай элементы с таким partition key и sort key в таком диапазоне». Всё, что не по ключу, делается через Scan — полный перебор всей таблицы. Scan дорогой и медленный, его избегают. Отсюда правило новичка: сперва выпиши все запросы приложения, потом подбери ключи так, чтобы каждый запрос попадал в Query.
aws dynamodb query \
--table-name Orders \
--key-condition-expression "pk = :u AND begins_with(sk, :prefix)" \
--expression-attribute-values '{":u":{"S":"USER#42"},":prefix":{"S":"ORDER#"}}'
Индексы: GSI и LSI
Часто нужно искать данные не только по основному ключу. Например, заказы хранятся по USER#42, но иногда надо найти заказ по его статусу. Для таких дополнительных способов поиска заводят вторичные индексы — это автоматически поддерживаемые копии данных с другим ключом.
GSI (Global Secondary Index, глобальный вторичный индекс) — отдельный индекс со своим partition key и sort key. У него своя пропускная способность, не зависящая от таблицы. GSI можно добавить в любой момент жизни таблицы — это удобно, когда появился новый сценарий поиска. Важное ограничение: чтение из GSI всегда eventually consistent (про это ниже), и это закладывают в дизайн.
LSI (Local Secondary Index, локальный вторичный индекс) — индекс с тем же partition key, что у таблицы, но другим sort key. У него три отличия от GSI, которые надо запомнить: его можно создать только в момент создания таблицы (потом не добавить и не удалить), он делит пропускную способность с таблицей, и для одного значения partition key суммарный объём данных вместе с LSI ограничен 10 ГБ. Зато LSI умеет отдавать strongly consistent чтения.
aws dynamodb create-table \
--table-name Orders \
--attribute-definitions AttributeName=pk,AttributeType=S AttributeName=status,AttributeType=S \
--key-schema AttributeName=pk,KeyType=HASH \
--global-secondary-indexes '[{"IndexName":"by-status","KeySchema":[{"AttributeName":"status","KeyType":"HASH"}],"Projection":{"ProjectionType":"ALL"}}]' \
--billing-mode PAY_PER_REQUEST
На практике GSI — основной инструмент для дополнительных способов доступа, потому что его гибче добавить и масштабировать. Каждый индекс — это копия данных (можно выбрать, какие атрибуты в неё проецировать), за которую вы платите местом и записями. Поэтому индексы заводят строго под конкретный запрос, а не «вдруг пригодится».
Режимы оплаты: on-demand и provisioned
DynamoDB берёт деньги за пропускную способность — за чтения и записи. Есть два режима.
On-demand (по требованию, PAY_PER_REQUEST) — вы платите за каждый запрос по факту, а ёмкость подстраивается под нагрузку мгновенно и сама. Это самый простой старт: ничего не настраиваешь, не угадываешь объём. Подходит для нового проекта и для неровной, скачущей нагрузки.
Provisioned (зарезервированный) — вы заранее задаёте количество единиц чтения и записи в секунду (можно включить авто-масштабирование, чтобы они менялись по графику). При стабильной предсказуемой нагрузке это заметно дешевле, потому что вы платите за зарезервированное, а не за каждый запрос.
Типичный путь: начать с on-demand, а когда нагрузка устаканится и станет понятен профиль, перейти на provisioned ради экономии. Подробнее про подсчёт денег в облаке — в материале про оптимизацию затрат.
Консистентность и single-table design
Консистентность — это про то, насколько свежие данные вы видите сразу после записи. В DynamoDB чтение по умолчанию eventually consistent (согласованность в конечном счёте): оно дешевле, но сразу после записи может на доли секунды вернуть чуть устаревшее значение, пока изменение разойдётся по копиям. Если нужна гарантия самых свежих данных, явно просят strongly consistent чтение — оно дороже и чуть медленнее. Запомните: чтение из GSI всегда только eventually consistent, выбора там нет.
Single-table design — продвинутый приём, когда разные типы сущностей (например, заказы и их позиции) кладут в одну таблицу, чтобы одним запросом достать связанные данные сразу. Это мощно и экономит обращения к базе, но проектировать такое сложно. Для многих сервисов проще и понятнее держать несколько отдельных таблиц. Это осознанный выбор под нагрузку, а не обязательная практика — начинающему почти всегда стоит начать с нескольких простых таблиц.
Когда DynamoDB, а когда реляционная база
Главная развилка, которую важно проговорить — DynamoDB и реляционная база (RDS) решают разные классы задач, ни одна не «лучше».
DynamoDB хорош, когда способы доступа известны заранее и стабильны, запросы простые и идут по ключу, а масштаб большой при требовании предсказуемой задержки. Типичные примеры: пользовательские сессии, профили, история событий, корзины покупок, лента уведомлений.
RDS (PostgreSQL, MySQL и подобные) нужен, когда запросы сложные: объединения нескольких таблиц (join), агрегаты и отчёты, транзакции, затрагивающие сразу несколько сущностей, и ситуации, где способ доступа к данным со временем меняется и хочется гибкости SQL. Если вы пока не знаете, какие запросы понадобятся, реляционная база прощает это лучше.
Простое правило: знаете запросы наперёд и их немного, нужен масштаб — берите DynamoDB и проектируйте ключи под запросы; нужна гибкость и сложные связи — берите реляционную базу.
Где это применяется
DynamoDB встречается почти в любом приложении на AWS, где есть «горячие» данные с понятным доступом по ключу и высокой нагрузкой: хранение сессий, профилей, флагов функций, корзин, лент событий. Особенно естественно он ложится в serverless-архитектуры на Lambda, где сервер не держат постоянно, а база должна масштабироваться сама и оплачиваться по факту.
Типичные ошибки начинающих: спроектировать таблицу «как в SQL» и потом упереться в Scan на каждом экране; выбрать partition key с малым числом значений и получить горячую партицию; добавить кучу GSI «на всякий случай» и платить за них; забыть, что LSI нельзя добавить после создания таблицы; ждать от GSI strongly consistent чтения, которого там нет; сразу полезть в single-table design без реальной потребности.
Что учить дальше: разберите соседние управляемые базы данных AWS, чтобы видеть всю палитру хранилищ; пройдите основы AWS и serverless, где DynamoDB используется чаще всего; загляните в объектное хранилище — это другой тип данных (файлы, бэкапы) и удобно понимать границу между ними. Полезно также посмотреть хорошо спроектированную архитектуру AWS — там выбор хранилища под задачу разобран как часть инженерных принципов.