Глубокое погружение в DynamoDB. Часть третья.
Глубокое погружение в DynamoDB. Часть третья 3: Консистентность, DynamoDB streams, TTL, Глобальные таблицы, DAX
История одной из быстрейших в мире СУБД, рассказанная простыми, понятными человеку словами.
- Часть 1: Основы
- Часть 2: Компоненты DynamoDB: таблицы, типы данных, индексы, единицы емкости(Capacity Units)
- Часть 3: Консистентность, DynamoDB streams. TTL. Глобальные таблицы. DAX (Вы находитесь здесь)
- Часть 4: Моделирование данных. Лучшие практики. Что дальше?
И снова здравствуйте!
В этой части мы с вами рассмотрим некоторые более продвинутые функции 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 и окончу это приключение поделившись с вами кучей ссылок, чтобы вы могли получить максимум полезного в вашем путешествии в мир бессерверных технологий.
Целую, обнимаю.
Примечание от переводчика
Перевод выполнен с разрешения автора оригинального текста.
Постарался сделать максимально приближенный перевод. Отдельные термины перевести не смог, просто не смог подобрать удобоваримый аналог в русском языке.
Оглавление ссылается на оригинальные статьи, после перевода – обновлю.
Перевод этой части занял довольно таки длительный промежуток времени по причине занятности в разных активностях. По возможности постараюсь ускорить перевод остальных частей, но это не точно.
На сем позвольте откланяться.