Глубокое погружение в DynamoDB. Часть первая: Основы
История одной из быстрейших в мире СУБД, рассказанная простыми, понятными человеку словами.

Пролог
Я должен признаться. Я очень люблю ScyllaDB. Для тех, кто не в курсе, Scylla – это нереляционная СУБД, совместимая с Cassandra и DynamoDB. Команда разработчиков состоит из бывших инженеров RedHat и разработчиков KVM.
Зная, что находится в основе Scylla, я внимательно наблюдаю за его развитием, чтобы увидеть во что это выльется…. перед тем, как AWS предложит его, как управляемую услугу.
Тем не менее, у меня есть проблема с Scylla, которая, к сожалению, была заложена при проектировании. Проблема заключается в том, что при использовании автоматического масштабирования кластера размером в террабайты данных, то есть добавлении и удалении узлов, происходит серьезное снижение производительности кластера, в том случае, если операция производится часто. Причина проста – при добавлении нового узла, он должен получить последний набор данных, таким образом новый узел будет скачивать данные с соседей. При удалении – кластер также должен повторно синхронизироваться.
Scylla, по замыслу разработчиков, имеет минимальный оверхед, таким образом, наилучшим способом ее использование является масштабирование в сторону увеличения ресурсов при увеличении нагрузки. Что, в свою очередь не считается лучшей практикой в AWS.
И хотя, я бы с удовольствием поработал со ScyllaDB, когда бы мне не предоставилась такая возможность, сегодня герой дня – DynamoDB. Amazon DynamoDB – это продукт, с богатым функционалом, отказоустойчивый по замыслу, чрезвычайно быстрый, с гарантированной производительностью, которую можно установить по требованию, и, что немало важно, бессерверный.
Использование бессерверного подхода является критичным для большинства пользователей AWS, которые являются представителями малого и среднего бизнеса и не могут позволить себе найм десятков системный администраторов и администраторов баз данных. DynamoDB, как полностью управляемая AWS СУБД, конечно же может привязать вас к вендору, однако преимущества для небольшого бизнеса значительно превышают недостатки.
В этой серии статей я собираюсь погрузиться вглубь DynamoDB. Я проведу вас сквозь внутренности этого мощного, обладающего широкими возможностями постоянного wide-column хранилища(или хранилища с расширяемыми записями) данных, с самых основ до моделирования данных, с создания таблиц до запуска DAX.
В ходе написания статей, я буду использовать документацию AWS и информацию из блогов, а также собственные знания. По окончании, я оставлю несколько полезных ссылок, чтобы вы могли получать удовольствие работая с DynamoDB!
Оглавление:
- Часть 1: Основы (Вы находитесь здесь!)
- Часть 2: Компоненты DynamoDB: таблицы, типы данных, индексы, единицы производительности
- Часть 3: Консистентность, DynamoDB streams. TTL. Глобальные таблицы. DAX
- Часть 4: Моделирование данных. Лучшие практики. Что дальше?
На старт! Внимание! Погружаемся!
Основы DynamoDB
Я полагаю, что вы уже знаете, что такое базы данных, какие существуют базы данных и в чем коренное различие между реляционными и нереляционными СУБД.
Amazon DynamoDB (далее я буду использовать аббревиатуру DDB) – это полуструктурированное wide-column хранилище. Его можно использовать как хранилище пар ключ-значение, так и для построения структурированной схемы. Все зависит от вас!
В отличии от той же Cassandra или Scylla, которые вы можете развернуть у себя или еще где-нибудь, DDB является бессерверным решением, что значит, что у вас нет возможности самостоятельно управлять инфраструктурой. У вас только есть доступ к API AWS, благодаря вызовам к которому вы можете создавать таблицы и индексы, а также выполнять операции CRUD (Create-Read-Update-Delete – Создания-Чтения-Обновления-Удаления). Вы работаете только с теми ресурсами, которые вам предоставляет DDB: таблицы, индексы и DAX-кластера(которые мы рассмотрим позднее).
Данные в DDB хранятся в таблицах. Таблицы состоят из элементов; каждый элемент должен иметь первичный ключ(Primary key). Первичный ключ состоит из ключа раздела(Partition key), кроме того, может включать в себя ключ сортировки(Sort key).

Мы познакомимся с таблицами и типами данных в следующих главах. А пока давайте запачкаем руки!
Примечание: Я буду использовать AWS CLI 2 и Python + Boto3 для работы с DynamoDB. Вы же можете выбрать любой язык, который вы хотетие, так как все официальные SDK AWS поддерживают DynamoDB.
Создание таблиц
Давайте создадим таблицу, в которой будут храниться данные персонажей MMORPG! Наш элемент (в реляционных базах данных это называется строкой) выглядит следующим образом:
{
"characterId": "7877e1b90fe2",
"playerId": "74477fae0c9f",
"serverRegion": "us-east",
"currentServer": "srv123.us-east.bestrpg.com",
"race": "human",
"class": "knight"
}
Что самое классное, относительно данного элемента, так это то, что мы сразу видим определенную схему! Так что я собираюсь сделать следующее:
aws dynamodb create-table \
--table-name characters \
--attribute-definitions \
AttributeName=characterId,AttributeType=S \
AttributeName=playerId,AttributeType=S \
--key-schema \
AttributeName=characterId,KeyType=HASH \
AttributeName=playerId,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
Как видите, хотя я знаю, какие атрибуты(столбцы в реляционных базах данных) у меня будут в схеме, но я могу обозначить только те атрибуты, которые будут использованы в первичном ключе. В приведенном выше примере я определяю два атрибута: characterId и playerId. Я объясню, что происходит в следующем разделе, но если совсем просто – я сообщил DDB API следующее:
Эй, Динамо! Я собираюсь создать таблицу для своей MMORPG-игры! Я ожидаю, что у моих игроков будет несколько персонажей, с которыми они будут играть, но каждый персонаж уникален и соответствует одному и только одному игроку.
Так что, дорогая, я хочу чтобы у меня была таблица characters, characterID типа String – ключ раздела (также известный, как HASH) и playerID типа String – ключ сортировки ( иначе называемый RANGE)!
Я пока не знаю, сколько WCU и RCU мне нужно, поэтому сейчас я буду использовать модель оплаты по требованию!
И, когда я запущу эту команду, я получу следующий ответ:
{
"TableDescription": {
"AttributeDefinitions": [
{
"AttributeName": "characterId",
"AttributeType": "S"
},
{
"AttributeName": "playerId",
"AttributeType": "S"
}
],
"TableName": "characters",
"KeySchema": [
{
"AttributeName": "characterId",
"KeyType": "HASH"
},
{
"AttributeName": "playerId",
"KeyType": "RANGE"
}
],
"TableStatus": "CREATING",
"CreationDateTime": 1602229598.69,
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 0,
"WriteCapacityUnits": 0
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:REGION:ACCT:table/characters",
"TableId": "UUID",
"BillingModeSummary": {
"BillingMode": "PAY_PER_REQUEST"
}
}
}
Который, по сути, является метаданными таблицы. Потребуется некоторое время на создание таблицы, а то, что происходит в это время под капотом – выходит за рамки данной серии статей, однако я бы рекомендовал вам посмотреть это видео с конференции re:Invent, где объясняется архитектура DDB.
Что я планирую делать дальше, так это записать кое-какие данные при помощи Python и SDK boto3. Но сначала я собираюсь узнать, как там поживает моя таблица:
# Import Python SDK for AWS
>>> import boto3
# Initialize DynamoDB resource object.
>>> ddb = boto3.resource('dynamodb')
# Declare DynamoDB table
>>> characters = ddb.Table('characters')
>>> print(characters.table_status)
ACTIVE
Запись и чтение из таблиц
Я вижу, что моя таблица готова действию, так что давайте что-нибудь в нее запишем!
>>> resp = characters.put_item(
... Item={
... "characterId": "7877e1b90fe2",
... "playerId": "74477fae0c9f",
... "serverRegion": "us-east",
... "currentServer": "srv123.us-east.bestrpg.com",
... "race": "human",
... "class": "knight"
... }
... )
Обратите внимание, что я отправляю словарный объект в DDB и, если я все сделаю правильно, проблем возникнуть не должно. Но, если я сделаю опечатку или не включу первичный ключ в значение элемента, я получу ошибку типа исключение. Например:
>>> resp = characters.put_item(Item={"foo": "bar"})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.8/site-packages/boto3/resources/factory.py", line 520, in do_action
response = action(self, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/boto3/resources/action.py", line 83, in __call__
response = getattr(parent.meta.client, operation_name)(*args, **params)
File "/usr/local/lib/python3.8/site-packages/botocore/client.py", line 357, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/usr/local/lib/python3.8/site-packages/botocore/client.py", line 676, in _make_api_call
raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the PutItem operation: One or more parameter values were invalid: Missing the key characterId in the item
Получение элемента (SELECT FROM) – аналогичная операция, но мне нужно указать только атрибуты Key (ключи раздела и сортировки):
>>> resp = characters.get_item(
... Key={
... "characterId": "7877e1b90fe2",
... "playerId": "74477fae0c9f"
... }
... )
>>> print(resp['Item'])
{'characterId': '7877e1b90fe2', 'race': 'human', 'playerId': '74477fae0c9f', 'currentServer': 'srv123.us-east.bestrpg.com', 'serverRegion': 'us-east', 'class': 'knight'}
Ключи раздела и сортировки должны быть предоставлены, поскольку они образуют первичный ключ (если я создал ключ сортировки во время создания таблицы; в противном случае требуется только ключ раздела). Предоставление только ключа раздела также вызовет исключение:
>>> resp = characters.get_item(Key={"characterId": "7877e1b90fe2"})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.8/site-packages/boto3/resources/factory.py", line 520, in do_action
response = action(self, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/boto3/resources/action.py", line 83, in __call__
response = getattr(parent.meta.client, operation_name)(*args, **params)
File "/usr/local/lib/python3.8/site-packages/botocore/client.py", line 357, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/usr/local/lib/python3.8/site-packages/botocore/client.py", line 676, in _make_api_call
raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the GetItem operation: The provided key element does not match the schema
>>>
Мы можем выполнить и другие операции с таблицами DDB, но мы рассмотрим их в следующих главах! Приготовьтесь, в следующей главе мы погрузимся по колено во внутренние компоненты и структуры DynamoDB! Оставайтесь с нами и увидимся через некоторое время!
Целую, обнимаю.
Примечание от переводчика
Перевод выполнен с разрешения автора оригинального текста.
Постарался сделать максимально приближенный перевод. Отдельные термины перевести не смог, просто не смог подобрать удобоваримый аналог в русском языке.
Оглавление ссылается на оригинальные статьи, после перевода – обновлю.
На сем откланяюсь