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

Глубокое погружение в DynamoDB. Часть третья 3: Консистентность, DynamoDB streams, TTL, Глобальные таблицы, DAX

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

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



И снова здравствуйте!

В этой части мы с вами рассмотрим некоторые более продвинутые функции DynamoDB. Мы с вами посмотрим, как интегрировать потоки данных с другими сервисами используя DynamoDB Streams, как построить мультирегиональную инфраструктуру СУБД используя Глобальные таблицы, как использовать DAX для кеширования, а также как применять TTL ( время жизни) к элементам.

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

Консистентность в DynamoDB

TL;DR: Если вам известны термины ACID и теорема CAP — DynamoDB это A/P.

С давних пор одним из основных вызовов при работе с базами данных – являлось предотвращение повреждения данных при параллельном доступе к данным. Для решения этой проблемы “Древние Инженера” изобрели и реализовали механизмы блокировки и транзакции. Внедрение эффективных и безопасных транзакционных технологий привело к созданию ACID, как набора необходимых свойств для СУБД.

Акроним ACID означает следующее:

  • Atomicity(Атомарность) — транзакция либо признается удачной после окончания, либо откатывается
  • Consistency(Консистентность) — каждая транзакция гарантирует изменение данных из одного состояния в другое
  • Isolation(Изоляция) — параллельные транзакции не влияют друг на друга
  • Durability(Стойкость) — удачная транзакция сохраняется в системе и не теряется в случае падения системы(не путать с потерей сервера!)

ACID реализуется благодаря командам SQL, таким как COMMIT и ROLLBACK. Помимо этого, диспетчер блокировки – один из компонентов СУБД, гарантирует то, что к одним и тем же данным не будут обращаться несколько транзакций одновременно (в программировании это называется Race Condition(Cостояние гонки))

В то же время, как, благодаря этим механизмам обеспечения безопасности, обеспечивается защита данных, производительность операций в СУБД, в основном операций записи – резко ухудшается с течением временем, увеличением объема данных и ростом активности.

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

Теорема CAP определяет следующее:

  • Consistency(Консистентность)
  • Availability(Доступность)
  • Partition Tolerance(Устойчивость к распределению)

В то время, как вы получаете C и A, Partition Tolerance определяет, можем ли мы восстановить данные, если часть их будет потеряна (даже навсегда). Теорема утверждает, что распределенная система может выполнить только 2 из 3 условий. CAP — захватывающая тема для изучения, поэтому обязательно изучите ее!

В то же время это не означает, что мы не можем иметь ACID-совместимых баз данных. Позвольте представить вам другой тип СУБД – BASE.

  • Basic Availability(Базовая доступность) — “скорее всего я обработаю ваш запрос”
  • Soft state(Гибкое состояние) — “давай я не буду гарантировать, что твои данные будут такими, какие они сейчас”
  • Eventual Consistency(Консистентность в конечном счете) — “если тебе повезет, ты прочтешь последнюю версию

Так, о чем это я? А, да, DynamoDB.

Основная идея DynamoDB состоит в том, чтобы обеспечить максимально эффективное и быстрое выполнение операций чтения и записи. Компромисс же заключается в консистентности данных.

Когда вы вызываете PutItem , а затем сразу же вызываете GetItem для тех же данных, есть вероятность, что вы получите в ответ старые данные. Есть несколько вещей о которых вы должны знать.


Однако реализовать консистентную операцию чтения довольно просто:

>>> characters.get_item(
...     Key={
...             'playerId': '7877e1b90fe2',
...             'characterId': '74477fae0c9f'
...     },
...     ConsistentRead=True
... )['Item']
{'race': 'human', 'level': Decimal('7'), 'health': Decimal('9000'), 'characterId': '74477fae0c9f', 'mana': Decimal('10'), 'playerId': '7877e1b90fe2', 'strength': Decimal('42'), 'speed': Decimal('23'), 'playerRegion': 'us-east', 'currentServer': 'srv01.us-east.bestrpg.com', 'class': 'knight', 'playerEmail': 'foo@bar.com'}

Где же подвох? А он спрятан в цене.

В то время, как операции чтения с нестрогой консистенцией потребляют только 0.5 RCU(Read Capacity Unit), операции чтения со строго консистенцией потребляют RCU целиком.

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

>>> characters.update_item(
...     Key={
...             'playerId': '7877e1b90fe2',
...             'characterId': '74477fae0c9f'
...     },
...     UpdateExpression = 'SET currentServer = :newserver',
...     ExpressionAttributeValues={
...             ':newserver': 'srv02.us-east.bestrpg.com'
...     }
... )

А теперь представьте, что мы случайно получили двойной запрос на перенос персонажа игрока на другой сервер(дупликация – распространенная ситуация в распределенных системах). В таком варианте, персонаж будет перенесен дважды и повлияет на прогресс игрока.

Чтобы избежать такой ситуации у нас имеется механизм записи с условием; В чем суть? А суть в том, что мы запускаем GetItem перед выполнением UpdateItem или PutItem.

>>> current = characters.get_item(
...      Key={
...             'playerId': '7877e1b90fe2',
...             'characterId': '74477fae0c9f'
...     },
...     ProjectionExpression = 'currentServer'
... )['Item']['currentServer']
>>> characters.update_item(
...     Key={
...             'playerId': '7877e1b90fe2',
...             'characterId': '74477fae0c9f'
...     },
...     UpdateExpression = 'SET currentServer = :newserver',
...     ExpressionAttributeValues={
...             ':newserver': 'srv02.us-east.bestrpg.com'
...     },
        KeyConditionExpression=Key('currentServer').eq(current)
... )

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

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

Представляю вам DynamoDB Streams!

DynamoDB Streams!

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

Технически мы можем отправить уведомление посредством SNS, или поместить сообщение в очередь SQS, или даже отправить вызов API к какому-нибудь сервису. В любом случае, существует более простой путь отлова изменений поддерживаемый DynamoDB из коробки.

DynamoDB Streams – это реализация CDC – Change Data Capture(Захват изменений данных), шаблона проектирования, который позволяет нам отслеживать изменения, происходящие тут и там.

Для того, чтобы включить DynamoDB Stream – нам необходимо обноввить настройки нашей таблицы.

$ aws dynamodb update-table \
               --table-name characters \
               --stream-specification \
               StreamEnabled=True,StreamViewType=NEW_AND_OLD_IMAGES{
# very long output...
"StreamSpecification": {
            "StreamEnabled": true,
            "StreamViewType": "NEW_AND_OLD_IMAGES"
        },
        "LatestStreamLabel": "2021-01-06T17:41:06.230",
        "LatestStreamArn": "arn:aws:dynamodb:REGION:ACCT_ID:table/characters/stream/2021-01-06T17:41:06.230"
    }
}

Да, это было легко и просто! Обратите внимание на параметр StreamViewType – он обозначает, что именно мы будем ложить в stream. На момент написания текущей статьи поддерживаются следующие варианты:

  • KEYS_ONLY — передавать только ключи изменяемых элементов
  • NEW_IMAGE — только новые изменения
  • OLD_IMAGE — только прошлое состояние
  • NEW_AND_OLD_IMAGES — новые изменения и прошлое состояние

Выбор того, что именно использовать, сильно зависит от вашей “покупательской способности” и бизнес-потребностей, так как DynamoDB Streams оплачивается по использованным RRU – Read Request Units. Поэтому не забудьте проверить раздел ценообразования DynamoDB перед включением!

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


Включения Streams недостаточно для того, чтобы обеспечить выполнения всей логики, которая нам необходима. Для этого, нам необходимо подготовить Lambda функцию и триггер вызова. Я надеюсь, что вы уже знаете и понимаете, как работает AWS Lambda. В общем, вот наша функция обработки потока:

import json

def handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps(event)
    }      

Данной функции потребуются следующие разрешения в IAM, чтобы получать события из потока:

{
             "Effect": "Allow",
             "Action": [
                 "dynamodb:DescribeStream",
                 "dynamodb:GetRecords",
                 "dynamodb:GetShardIterator",
                 "dynamodb:ListStreams"
             ],
             "Resource":
 "arn:aws:dynamodb:region:accountID:table/characters/stream/*"         }

Отлично! Давайте теперь создадим

Примечание: если вы предпочитаете использовать AWS Console (Я не осуждаю!), то вы можете создать триггер прямо в консоли DynamoDB. В любом случае, вся работа будет происходить на стороне Lambda, а не DynamoDB.

Создание триггера для DynamoDB Streams ничем не отличается от такового для любого другого источника событий.

$ aws lambda create-event-source-mapping \
      --function-name streams \
      --event-source characters_stream_arn  \
      --batch-size 1 \
      --starting-position TRIM_HORIZON

--batch-size определяет, как много записей я передаю функции. --starting-position – это позиция откуда начинать чтение и применяется только к DynamDB Streams, Kinesis и MSK. TRIM_HORIZON – означает “что, что находится в потоке!” (для добавления функций к существующим рабочим нагрузкам я бы предложил использовать LATEST).

Как только моя функция будет подписана на изменения – я смогу ее проверить. Давайте выполним изменение существующего элемента и посмотрим, что получит Lambda.

>>> chars.update_item(
...     Key={
...             'playerId': '7877e1b90fe2',
...             'characterId': '74477fae0c9f'
...     },
...     UpdateExpression = 'SET currentServer = :newserver',
...     ExpressionAttributeValues={
...             ':newserver': 'srv03.us-west.bestrpg.com'
...     }
... )

Теперь я собираюсь посмотреть логи функции обрабатывающей поток.

{
  'Records': [
    {
      'eventID': '4699f01181c775a95d63bc3fc518427f', 
      'eventName': 'MODIFY', 
      'eventVersion': '1.1', 
      'eventSource': 'aws:dynamodb', 
      'awsRegion': 'us-east-1', 
      'dynamodb': 
        {
          'ApproximateCreationDateTime': 1610028408.0, 
          'Keys': 
            {
              'characterId': {'S': '74477fae0c9f'}, 
              'playerId': {'S': '7877e1b90fe2'}
            }, 
          'NewImage': 
            {
              'characterId': {'S': '74477fae0c9f'}, 
              'currentServer': {'S': 'srv02.us-east.bestrpg.com'},
              'playerId': {'S': '7877e1b90fe2'},
              # the rest...
            },
          'OldImage': 
            {
              'characterId': {'S': '74477fae0c9f'}, 
              'currentServer': {'S': 'srv03.us-west.bestrpg.com'},
              'playerId': {'S': '7877e1b90fe2'},
              # the rest... 
           }, 
# and many many useful meta data...
  ]
}

Ага! В общем, объект Event содержит массив обновленных записей (блок DynamoDB) с новыми изменениями и старыми значениями. Также значение имени события(eventName) – MODIFY

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

import json
def notify_player(email, new_server):
    print(f'Notified player {email} to move to the {new_server}')def decommission_server(old_server):
    print(f'Decommissioned {old_server}')def lambda_handler(event, context):
    statusCode = 200
    message = ''
    event_name = event['Records'][0]['eventName']
    change = event['Records'][0]['dynamodb']
    old_server = change['OldImage']['currentServer']['S']
    new_server = change['NewImage']['currentServer']['S']
    email = change['NewImage']['playerEmail']['S']
    if event_name == 'MODIFY':
        if new_server != old_server:
            notify_player(email, new_server)
            decommission_server(old_server)
            message = 'Did useful stuff!'return {
        'statusCode': 200,
        'body': {
            'message': message
        }
    }

Примечание: вышеобозначенный код не защищен от ошибок. Мы не знаем, что делать если вы получите OldImage, когда не производите изменения с текущими элементами, а создаете новый. А как насчет DeleteItem? В общем мы можем столкнуться с разными порблемами, поэтому, пожалуйста не копируйте этот код в ваш продакшн!

Давайте еще раз обновим таблицу и посмотрим логи

Ура!

Однако есть еще несколько вещей, которые вы должны знать, прежде чем использовать DynamoDB Streams.

Во первых, DynamoDB Streams интегрируется только с AWS Lambda. Если вы хотите использовать других потребителей, то вам стоит интегрировать DynamoDB с Kinesis Streams. Во вторых, изменение в потоке живет 24 часа. К слову, о времени жизни…

DynamoDB Item TTL (Время жизни элемента в DynamoDB)

DynamoDB — отличная база данных для хранения и просмотра отдельных элементов данных. Однако могут быть случаи, когда нам не нужно, чтобы данные жили в базе вечно. Для этой цели существует еще одна функция, называемая TTL (Time-To-Live – Время жизни).

Предположим, мы запускаем пакетное задание, которое сбрасывает много внутриигрового лута(добычи в игровом сленге) в случайных местах на карте. Каждый элемент типа «лут» выглядит следующим образом:

{
  'locationId': 'ewref2214', # partition key  
  'lootId': 'fdjijr23q21',   # sort key
  'rarity': 'rare',
  'lattitude': '48.856144',
  'longitude':  '2.297820'
}

Периодически сервер будет сканировать таблицу, чтобы получить все предметы и разместить внутриигровые предметы на карте.

Если я хочу определить срок существования лута, например, удалить его с карты по истечению времени, я могу хранить TTL на сервере, и он автоматически удалит лут с карты, но при этом мне также придется вручную удалить объект из таблицы, что обойдется мне как минимум в один WCU за каждый внутриигровой предмет.

Или я могу использовать DynamoDB TTL!

Преимущество TTL в том, что это происходит в фоновом режиме, и с меня не взимается плата за удаление. Чтобы использовать его, я добавлю TTL к объекту лута.

{
  'locationId': 'ewref2214', # partition key  
  'lootId': 'fdjijr23q21',   # sort key
  'rarity': 'rare',
  'lattitude': '48.856144',
  'longitude':  '2.297820',
  'ttl': 1610471877 # must be a UNIX epoch time
}

И включу TTL для элементов в таблице:

$ aws dynamodb update-time-to-live \
               --table-name loot \
               --time-to-live-specification \
               "Enabled=true,AttributeName=ttl"

AttributeName=ttl – соотносится с атрибутом элемента. Когда TTL включен, серверная часть DynamoDB сравнивает атрибут TTL с текущим временем сервера и удаляет элемент из таблицы и каждого индекса, если срок его жизни истек. Если включен DynamoDB Streams, то событие изменения также будет отправлено для обработки, если это необходимо.

Если у элемента нет атрибута TTL, он не будет удален фоновым процессом, что позволяет нам совмещать заменяемые и постоянные данные в одной и той же таблице.

TTL легко реализовать и просто использовать. А при совместном использовании с DynamoDB Streams мы даже можем превратить DynamoDB в реал-тайм стриминг платформу. А используя Глобальные таблицы – в практические реал-тайм стриминг платформу планетарного масштаба.

Постойте, я что, только что произнес “Глобальные Таблицы”?

DynamoDB Global Tables (Глобальные таблицы DynamoDB)

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

Глобальные таблицы DynamoDB — это несколько таблиц (также называемых репликами), которые обмениваются изменениями друг с другом. Их можно создавать в разных регионах AWS в одной учетной записи.

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

Обязательным требованием для работы Глобальных Таблиц является работающий DynamoDB Streams с опцией NEW_AND_OLD_IMAGES, который, мы, к слову, включили ранее для таблицы игровых персонажей.

Моя таблица размещена в us-east-1 и я хочу иметь реплики в us-west-1, eu-west-1 (Ирландия), eu-central-1 (Германия), and ap-southeast-1 (Сингапур). Я выполняю следующую команду:

$ aws dynampdb update-table \
               --table-name characters \
               --replica-updates \
              '{"Create": {"RegionName": "us-west-1"}}'

Потребуется некоторое время для создания реплики в другом регионе.

$ aws dynamodb describe-table \
               --table-name characters \
               --query 'Table.TableStatus' \
               --output text
UPDATING

Как только статус снова станет ACTIVE, я смогу сделать мое приложение по-настоящему распределенным. Давайте попробуем. Я запущу две отдельные сессии Boto3 для работы в разных регионах.

# I cannot create a resource object with a region,
# as region endpoint is passed to the Client or Session object
# So first I need two sessions.
>>> session_east = boto3.session.Session(region_name='us-east-1')
>>> session_west = boto3.session.Session(region_name='us-west-1')# Now I can create 2 Resource and Table objects
# corresponding to the respective region>>> ddb_east = session_east.resource('dynamodb')
>>> ddb_west = session_west.resource('dynamodb')
>>> characters_east = ddb_east.Table('characters')
>>> characters_west = ddb_west.Table('characters')

Теперь я создам элемент в таблице characters в регионе us-east-1 и прочту этот объект в регионе us-west-1.

>>> characters_east.put_item(
...     Item={
...             'playerId': 'foo1234', 
...             'characterId': 'bar5678'
...      }
... )
>>> characters_west.get_item(
...     Key={
...             'playerId': 'foo1234', 
...             'characterId': 'bar5678'
...     }
... )['Item']
{'characterId': 'bar5678', 'playerId': 'foo1234'}

Отлично, объект появился уже через миллисекунды.

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

>>> characters_west.put_item(
...     Item={
...             'playerId': 'foobar1234',
...             'characterId': 'barfoo5678'
...     }
... )
>>> characters_east.get_item(
...     Key={
...             'playerId': 'foobar1234',
...             'characterId': 'barfoo5678'
...     }
... )['Item']
{'characterId': 'barfoo5678', 'playerId': 'foobar1234'}

Мы рассмотрим Глобальные таблицы еще немного в следующей части, когда мы будем разговаривать о лучших практиках. А теперь переходим к последней теме на сегодня – DynamoDB Accelerator или DAX.

DAX

Разработчики систем баз данных делают все возможное, чтобы создавать самые быстрые в мире базы данных. Однако получение данных прямо из оперативной памяти компьютера всегда быстрее, чем чтение из постоянного хранилища(подразумевается дисковая подсистема хранения данных). То же самое относится и к DynamoDB, поэтому команда разработчиков AWS реализовала проприетарную систему кэширования под названием DAX. В отличие от ElastiCache для Redis, DAX действует как прокси-сервер перед таблицей DynamoDB, используя обе стратегии кэширования: отложенную загрузку(Lazy Loading) и сквозную запись(Write-Through).

Создание кластера DAX сложнее, чем создание таблицы, поскольку нам потребуются дополнительные ресурсы, например, подсети и роль IAM с разрешениями на доступ к таблице. Ниже приведен шаблон CloudFormation для создания кластера.

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  TableName:
    Type: String
  ClusterSize:
    Type: Number
  Subnets:
    Type: List<AWS::EC2::Subnet::Id>

Resources:
  # This role provides DAX cluster nodes with access to DynamoDB
  DaxRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service: "dax.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: DAX
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: "Allow"
                Action: "dynamodb:*"
                Resource:
                  - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${TableName}"
  
  # DAX Subnet Group is a set of VPC Subnets where the nodes are provisioned
  DaxSubnetGroup:
    Type: AWS::DAX::SubnetGroup
    Properties:
      SubnetIds: !Ref "Subnets"
  
  # DAX Cluster
  DaxCluster:
    Type: AWS::DAX::Cluster
    Properties:
      IAMRoleARN: !GetAtt  "DaxRole.Arn"
      # usually it is a best practice to place instance types
      # outside of resource definition... But I'm too lazy
      NodeType: "dax.t2.small" 
      ReplicationFactor: !Ref "ClusterSize"
      SubnetGroupName: !Ref "DaxSubnetGroup"

Outputs: 
  DaxClusterEndpoint:
    Value: !GetAtt "DaxCluster.ClusterDiscoveryEndpoint"

Файл можно скачать по ссылке – https://gist.github.com/Th0masStorm/a4119eddd83b2a4edce64aac2b10dfb0#file-dax-yaml

Теперь я разверну стэк:

$ aws cloudformation deploy \
                     --stack-name dax \
                     --template-file dax.yaml \
                     --parameter-overrides \
                     TableName=characters \
                     ClusterSize=1 \
                     Subnets="subne

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

$ aws cloudformation describe-stacks \
                     --stack-name dax \
                     --query "Stacks[].Outputs[].OutputValue" \
                     --output text
uglystring.clustercfg.dax.use1.cache.amazonaws.com:8111

Также мне потребуется создать роль для инстанса EC2, чтобы он мог получить доступ к DAX и DynamoDB

{
     "Version": "2012-10-17",
     "Statement": [
         {
             "Action": [
                 "dax:*"
             ],
             "Effect": "Allow",
             "Resource": [
                 "arn:aws:dax:region:acct_id:cache/uglystring"
             ]
         }
     ]
}

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

$ sudo yum -y install python3-pip
$ pip3 install amazon-dax-client boto3 --user

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

>>> import boto3
>>> from amazondax import AmazonDaxClient
>>> dax = AmazonDaxClient.resource(
...           endpoint_url="verylongurl:8111", 
              # If you don't add region name
              # you most likely will get a routing issue
...           region_name='us-east-1')
>>> characters = dax.Table('characters')
>>> characters.scan()['Items']
[{'playerId': 'foobar1234', 'characterId': 'barfoo5678'}, {'playerId': 'foo1234', 'characterId': 'bar5678'}, {'playerId': '7877e1b90fe2', 'characterId': '74477fae0c9f', 'class': 'knight', 'currentServer': 'srv10.us-east.bestrpg.com', 'health': Decimal('9000'), 'level': Decimal('7'), 'mana': Decimal('10'), 'playerEmail': 'foo@bar.com', 'playerRegion': 'us-east', 'race': 'human', 'speed': Decimal('23'), 'strength': Decimal('42')}]

Более того, я даже могу добавить элемент в таблицу через DAX

>>> characters.put_item(
...     Item={
...             'playerId': 'createdByDax',
...             'characterId': 'createdByDax'
...     }
... )
# I will create a new connection directly to Dynamodb
>>> ddb = boto3.resource('dynamodb')
>>> characters = ddb.Table('characters')
>>> characters.get_item(
...     Key={
...             'playerId': 'createdByDax',
...             'characterId': 'createdByDax'
...     }
... )['Item']
{'characterId': 'createdByDax', 'playerId': 'createdByDax'}

Великолепно! <! –В данном контексте скорее произносится с теми же интонациями, что и “Ачуметь!” –>

Еще несколько моментов, о которых стоит сказать, прежде, чем мы закончим. DAX – это сервис, развертывающийся на инстансах, так что мы платим за подлежащие ноды, траффик, RCU/WCU между DAX и DynamoDB. Кроме того, DAX поддерживает LSI и GSI, так что вы никак не ограничены в функциональности.


Это третья и почти заключительная часть в серии статей Глубокое Погружение в DynamoDB. Мы вместе с вами прошли длинный путь и уже близки к финалу.

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

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

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

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

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

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

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

На сем позвольте откланяться.

Leave a Reply