Если вы годами работали с реляционными базами данных, начало работы с DynamoDB от Amazon может показаться вам пугающим. Традиционные реляционные базы данных были оптимизированы для хранения; DynamoDB оптимизирован для повышения производительности. Таблицы существуют независимо; нет предложения «Join» для одновременного запроса записей из более чем одной таблицы. Такие концепции, как нормализация (отказ от хранения повторяющихся данных) больше не являются критичными; на самом деле, иногда ваша модель данных явно дублирует данные для повышения производительности.

Цель этой статьи - изучить основы DynamoDB, погрузившись в нее и используя ее. Мы разработаем схему DynamoDB для отслеживания высоких результатов для многих пользователей в нескольких играх. Мы также будем использовать консольное приложение .NET Core для заполнения и тестирования нашей базы данных.

Настраивать

Если вы предпочитаете сразу погрузиться в схему БД и исходный код, все, что вам нужно, есть в этом репозитории GitHub. Вы найдете файл Terraform, который описывает нашу готовую схему таблицы DynamoDB. Если вы раньше не использовали Terraform, это фантастический инструмент, который позволяет вам писать свою инфраструктуру в виде кода.

Единственное предварительное условие, необходимое для использования Terraform, - это наличие на вашем компьютере общих учетных данных AWS (если вы когда-либо использовали AWS CLI, все в порядке). Затем скачайте Terraform и добавьте его в свой путь. Отредактируйте файл example.tf, чтобы изменить профиль на имя вашего собственного профиля AWS. Затем откройте командную строку в каталоге, содержащем файл .tf, и выполните следующие две команды:

Terraform выполнит все API-интерфейсы AWS, необходимые для создания вашей инфраструктуры. Если вы внесете изменения в свой файл терраформа, вы можете снова запустить «терраформ применить», чтобы выполнить обновления. Когда вы закончите использовать его, выполнение «terraform destroy» удалит все ваши изменения из среды AWS.

Включенное консольное приложение .NET Core использует AWS SDK для подключения к нашей таблице DynamoDB, заполнения ее случайными данными и выполнения запросов к ней.

DynamoDB

Чтобы успешно внедрить DynamoDB, вам необходимо сначала понять желаемый шаблон доступа. Какие проблемы мы пытаемся решить? Как наши приложения будут запрашивать нашу базу данных?

  • Каковы высокие баллы пользователя во всех играх?
  • Каков высокий балл для пользователя в конкретной игре?
  • Какой самый последний рекорд был достигнут пользователем?
  • Каков общий высокий балл для game_id и какой пользователь его набрал?

В нашей таблице мы будем отслеживать четыре атрибута:

  • Имя пользователя (строка)
  • Игра (строка)
  • TopScore (число)
  • Отметка времени (строка)

Запрос против сканирования

Прежде чем приступить к работе со схемой, важно понять два способа, которыми мы обычно взаимодействуем с нашей базой данных. Мы можем сканировать таблицу, что означает изучение каждой отдельной строки, чтобы определить, подходит ли эта запись к применяемому нами фильтру. Или мы можем запросить таблицу, в которой используются индексы (мы обсудим это ниже), чтобы исследовать только небольшой диапазон элементов. При разработке нашей базы данных цель обычно состоит в том, чтобы избежать сканирования чего-либо, кроме самых маленьких таблиц, потому что они могут быть очень медленными и даже использовать всю емкость чтения вашей таблицы на короткий период (вызывая сбой других запросов).

Основной ключ

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

Ключ раздела

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

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

В нашем примере Username будет хорошим ключом раздела, потому что он предоставляет разнообразный диапазон уникальных записей. Но поскольку мы хотим сохранить высокий балл для каждой комбинации имени пользователя и игры, имя пользователя не будет уникальным в нашей таблице, и это не будет удовлетворять требованиям первичного ключа. Нам понадобится ключ сортировки.

Ключ сортировки

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

Например, мы можем запросить результаты, которые соответствуют определенному значению для ключа сортировки, элементы, которые больше или меньше значения, или элементы в диапазоне значений.

Использование Game в качестве ключа сортировки решает наш первый и второй варианты использования. Теперь у нас есть уникальный первичный ключ (составной из имени пользователя и игры), и мы можем запрашивать только ключ раздела, чтобы найти каждый из наивысших баллов для конкретного пользователя. Мы также можем запросить рекорды для конкретной комбинации имени пользователя и игры.

Местный вторичный индекс

Для нашего следующего варианта использования мы хотим знать, какой самый последний рекорд был достигнут пользователем. Но так как нашим ключом сортировки является игра, у нас нет эффективного способа фильтровать результаты по отметке времени. Мы могли бы использовать сканирование вместо запроса, но это означает просмотр каждой записи в таблице и затем применение фильтра. Это медленно и, в зависимости от размера таблицы, потенциально может использовать всю вашу подготовленную емкость чтения.

Нам в основном нужен другой ключ сортировки - к счастью, DynamoDB предоставляет его в виде локального вторичного индекса. Для краткости LSI можно определить только при создании таблицы; они не могут быть добавлены позже. LSI использует тот же ключ раздела, который уже определен, но позволяет выбрать новый ключ сортировки. В нашем сценарии мы будем использовать метку времени в качестве ключа сортировки LSI. Теперь мы можем запросить наш новый индекс, чтобы получить самый последний рекорд. Обратите внимание, что мы используем ScanIndexForward = false для сортировки результатов в порядке убывания и Limit = 1 для возврата одного результата.

Глобальный вторичный индекс

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

Лучший способ приблизиться к этому - использовать глобальный вторичный индекс (или GSI). Вы можете создать его в любое время, даже после создания таблицы, указав новый ключ раздела и, при необходимости, ключ сортировки. Оба эти ключа могут отличаться от тех, которые использовались для первоначального создания таблицы. Фактически мы создаем дублирующую версию нашей таблицы, но по-другому организуем данные. Наряду с ключами мы можем выбрать, какие еще атрибуты таблицы будут включены в этот новый индекс. Чтобы уменьшить объем хранилища и чтения / записи, эти атрибуты должны быть ограничены только тем, что необходимо для вашего варианта использования.

Чтобы найти глобальный рекорд для игры, нам нужно, чтобы все результаты находились в одном разделе. Итак, мы создадим GSI с игрой в качестве ключа раздела и TopScore в качестве ключа сортировки.

Резюме

DynamodDB не подходит для каждого случая использования, но он отлично подходит в ситуациях, когда вам нужен поиск значений ключа или вы можете использовать его разделы для выполнения запросов к небольшому диапазону записей. Тот факт, что это управляемое бессерверное предложение AWS, также привлекает разработчиков. Он также прекрасно интегрируется с AWS Lambda; Фактически, эти две службы часто составляют основу Alexa Skill.

DynamoDB также поддерживает Streams, функцию, которую мы здесь не обсуждали. Потоки позволяют фиксировать изменения в вашей таблице для использования другой службой. Например, вы можете отправить электронное письмо пользователю, когда он достигнет нового рекорда. Чтобы узнать, как использовать настройку Stream и подключить его к Lambda, ознакомьтесь с моей следующей статьей.

Код и терраформа для этих примеров доступны на GitHub. Пожалуйста, поделитесь отзывами и вопросами в комментариях. Спасибо!