Глубокое погружение в DynamoDB. Часть первая: Основы

Автор оригинала – Solution Architect, Creative Problem Solver, Pure Engineering Advocate, World-Class FizzBuzz Developer, AWS APN Ambassador EMEA, Data Community Builder Карен Товмасян

История одной из быстрейших в мире СУБД, рассказанная простыми, понятными человеку словами.

Пролог

Я должен признаться. Я очень люблю ScyllaDB. Для тех, кто не в курсе, Scylla – это нереляционная СУБД, совместимая с Cassandra и DynamoDB. Команда разработчиков состоит из бывших инженеров RedHat и разработчиков KVM.

Зная, что находится в основе Scylla, я внимательно наблюдаю за его развитием, чтобы увидеть во что это выльется…. перед тем, как AWS предложит его, как управляемую услугу.

Тем не менее, у меня есть проблема с Scylla, которая, к сожалению, была заложена при проектировании. Проблема заключается в том, что при использовании автоматического масштабирования кластера размером в террабайты данных, то есть добавлении и удалении узлов, происходит серьезное снижение производительности кластера, в том случае, если операция производится часто. Причина проста – при добавлении нового узла, он должен получить последний набор данных, таким образом новый узел будет скачивать данные с соседей. При удалении – кластер также должен повторно синхронизироваться.

Scylla, по замыслу разработчиков, имеет минимальный оверхед, таким образом, наилучшим способом ее использование является масштабирование в сторону увеличения ресурсов при увеличении нагрузки. Что, в свою очередь не считается лучшей практикой в AWS.

И хотя, я бы с удовольствием поработал со ScyllaDB, когда бы мне не предоставилась такая возможность, сегодня герой дня – DynamoDB. Amazon DynamoDB – это продукт, с богатым функционалом, отказоустойчивый по замыслу, чрезвычайно быстрый, с гарантированной производительностью, которую можно установить по требованию, и, что немало важно, бессерверный.

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


В этой серии статей я собираюсь погрузиться вглубь DynamoDB. Я проведу вас сквозь внутренности этого мощного, обладающего широкими возможностями постоянного wide-column хранилища(или хранилища с расширяемыми записями) данных, с самых основ до моделирования данных, с создания таблиц до запуска DAX.

В ходе написания статей, я буду использовать документацию AWS и информацию из блогов, а также собственные знания. По окончании, я оставлю несколько полезных ссылок, чтобы вы могли получать удовольствие работая с DynamoDB!

Оглавление:

На старт! Внимание! Погружаемся!

Основы 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 Blog

Мы познакомимся с таблицами и типами данных в следующих главах. А пока давайте запачкаем руки!

Примечание: Я буду использовать 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! Оставайтесь с нами и увидимся через некоторое время!

Целую, обнимаю.

Примечание от переводчика

Перевод выполнен с разрешения автора оригинального текста.

Постарался сделать максимально приближенный перевод. Отдельные термины перевести не смог, просто не смог подобрать удобоваримый аналог в русском языке.

Оглавление ссылается на оригинальные статьи, после перевода – обновлю.
На сем откланяюсь

Leave a Reply