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

Введение

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

Время от времени мы хотим очистить наши таблицы базы данных, чтобы сохранялись только ценные и значимые записи. Например, мы провели нагрузочное тестирование, чтобы протестировать API создания учетной записи пользователя в нашем приложении. Это, очевидно, привело к появлению множества записей в таблице, которые являются просто «тестовыми» записями. Все эти «тестовые» записи имеют характеристики электронной почты, начинающиеся с testing, и фамилии, начинающиеся с TEST. Вы знаете, что угодно. 😆 Эти записи, очевидно, можно будет удалить из таблицы после завершения нагрузочного тестирования.

Что вы узнаете

В этом руководстве вы узнаете, как использовать Python для вставки элементов и условного удаления элементов из таблицы DynamoDB, которые вы больше не хотите сохранять, по каким бы то ни было причинам. Мы собираемся использовать официальную библиотеку AWS SDK для Python, которая известна как Boto3.

Предварительные условия

Настроить виртуальную среду

Я не собираюсь подробно рассказывать об этом, но быстро покажу вам, как это сделать с помощью virtualenv.

Перво-наперво вам нужно установить virtualenv через pip3, если вы еще этого не сделали. Это очень просто:

$ ~/demo > pip3 install virtualenv

Затем создайте каталог, в который вы хотите поместить файл проекта (или, в данном случае, скрипт Python).

$ ~/demo > mkdir batch-ops-dynamodb

Перейдите в каталог и создайте там виртуальную среду.

$ ~/demo > cd ./batch-ops-dynamodb
$ ~/demo/batch-ops-dynamodb > virtualenv ./venv
Using base prefix '/Library/Frameworks/Python.framework/Versions/3.8'
New python executable in /Users/billyde/demo/batch-ops-dynamodb/venv/bin/python3.8
Also creating executable in /Users/billyde/demo/batch-ops-dynamodb/venv/bin/python
Installing setuptools, pip, wheel...
done.

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

$ ~/demo/batch-ops-dynamodb > source ./venv/bin/activate
$ ~/demo/batch-ops-dynamodb (venv) >

Обратите внимание на (venv). Это означает, что вы находитесь в виртуальной среде. Однако вы можете этого не увидеть, в зависимости от настроек вашего терминала. Итак, в качестве альтернативы вот почему вы можете проверить, активна ваша виртуальная среда или нет.

$ ~/demo/batch-ops-dynamodb (venv) > which python
/Users/billyde/demo/batch-ops-dynamodb/venv/bin/python
$ ~/demo/batch-ops-dynamodb (venv) > which pip
/Users/billyde/demo/batch-ops-dynamodb/venv/bin/pip

Вы можете видеть, что исполняемые файлы Python и pip - это файлы из нашей виртуальной среды. Все хорошо. 🙂

Установить зависимость

Единственная зависимость для этого руководства - это библиотека Boto3. Итак, давайте продолжим и установим его в нашей виртуальной среде.

$ ~/demo/batch-ops-dynamodb (venv) > pip install boto3
Collecting boto3
  Downloading boto3-1.11.7-py2.py3-none-any.whl (128 kB)
     |████████████████████████████████| 128 kB 1.0 MB/s
Collecting botocore<1.15.0,>=1.14.7
  Downloading botocore-1.14.7-py2.py3-none-any.whl (5.9 MB)
     |████████████████████████████████| 5.9 MB 462 kB/s
Collecting s3transfer<0.4.0,>=0.3.0
  Downloading s3transfer-0.3.1-py2.py3-none-any.whl (69 kB)
     |████████████████████████████████| 69 kB 2.1 MB/s
Collecting jmespath<1.0.0,>=0.7.1
  Using cached jmespath-0.9.4-py2.py3-none-any.whl (24 kB)
Collecting urllib3<1.26,>=1.20
  Downloading urllib3-1.25.8-py2.py3-none-any.whl (125 kB)
     |████████████████████████████████| 125 kB 5.3 MB/s
Collecting python-dateutil<3.0.0,>=2.1
  Using cached python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
Collecting docutils<0.16,>=0.10
  Using cached docutils-0.15.2-py3-none-any.whl (547 kB)
Collecting six>=1.5
  Downloading six-1.14.0-py2.py3-none-any.whl (10 kB)
Installing collected packages: urllib3, six, python-dateutil, docutils, jmespath, botocore, s3transfer, boto3
Successfully installed boto3-1.11.7 botocore-1.14.7 docutils-0.15.2 jmespath-0.9.4 python-dateutil-2.8.1 s3transfer-0.3.1 six-1.14.0 urllib3-1.25.8

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

Вращайте локальный экземпляр DynamoDB

Чтобы помочь нам увидеть, как работает скрипт, мы развернем локальный экземпляр DynamoDB в контейнере Docker. Вы можете следовать этому руководству, чтобы добиться этого.

По сути, вам нужно развернуть DynamoDB Docker и создать таблицу demo-customer-info, как написано в руководстве.

Создать фиктивные записи в таблице

Давайте создадим фиктивные записи, чтобы увидеть, как работает пакетная операция. Для этого мы собираемся написать сценарий Python, который в цикле вызывает операцию DynamoDB PutItem. Создайте новый файл Python в batch-ops-dynamo и назовите его insert_dummy_records.py.

~/demo/batch-ops-dynamodb ❯ touch insert_dummy_records.py

Как упоминалось ранее в разделе «Введение», наши фиктивные записи будут иметь следующие характеристики:

  • фамилия начинается с TEST
  • адрес электронной почты начинается с testing

В нашем скрипте будут следующие компоненты:

  • insert_dummy_record: функция, выполняющая PutItem операцию
  • for loop: цикл, который будет вызывать insert_dummy_record функцию 10 раз для вставки фиктивных записей.

Мы также используем метод random.randint для генерации случайных целых чисел, которые нужно добавить к атрибутам наших фиктивных записей, а именно:

  • customerId
  • lastName
  • emailAddress

Наш сценарий выглядит неплохо! Теперь пришло время запустить его из командной строки, используя исполняемый файл Python из нашей виртуальной среды.

~/demo/batch-ops-dynamodb ❯ python3 insert_dummy_records.py
Inserting record number 1 with customerId 769
Inserting record number 2 with customerId 885
Inserting record number 3 with customerId 873
Inserting record number 4 with customerId 827
Inserting record number 5 with customerId 231
Inserting record number 6 with customerId 199
Inserting record number 7 with customerId 272
Inserting record number 8 with customerId 268
Inserting record number 9 with customerId 729
Inserting record number 10 with customerId 289

В порядке. Скрипт работает, как ожидалось. Ура! 😄

Давайте проверим, вызвав операцию scan в нашей локальной таблице DynamoDB demo-customer-info, чтобы проверить записи.

~/demo/batch-ops-dynamodb ❯ aws dynamodb scan --endpoint-url http://localhost:8042 --table-name demo-customer-info
{
    "Items": [
        {
            "customerId": {
                "S": "199"
            },
            "lastName": {
                "S": "TEST199"
            },
            "emailAddress": {
                "S": "[email protected]"
            }
        },
        {
            "customerId": {
                "S": "769"
            },
            "lastName": {
                "S": "TEST769"
            },
            "emailAddress": {
                "S": "[email protected]"
            }
        },
... truncated
... truncated
... truncated
        {
            "customerId": {
                "S": "827"
            },
            "lastName": {
                "S": "TEST827"
            },
            "emailAddress": {
                "S": "[email protected]"
            }
        }
    ],
    "Count": 10,
    "ScannedCount": 10,
    "ConsumedCapacity": null
}

Идеально! Теперь у нас в таблице есть фиктивные записи.

Вставьте «настоящие» записи в таблицу

Мы быстро вставим в таблицу две «настоящие» записи. Для этого мы просто собираемся написать еще один скрипт Python, который принимает аргумент командной строки в качестве входных данных. Назовем файл insert_real_record.py.

~/demo/batch-ops-dynamodb ❯ touch insert_real_record.py

Содержимое файла будет следующим.

Давайте вставим в таблицу 2 записи.

~/demo/batch-ops-dynamodb ❯ python insert_real_record.py 11111 jones [email protected]
Inserting record with customerId 11111
~/demo/batch-ops-dynamodb ❯ python insert_real_record.py 22222 smith [email protected]
Inserting record with customerId 22222

Напишите скрипт для удаления записей по условиям

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

Опять же, как напоминание, мы хотим удалить вставленные фиктивные записи. Нам нужно применить фильтр: фамилия начинается с TEST, а адрес электронной почты начинается с testing.

Как работает скрипт:

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

Продолжайте и запустите этот сценарий из командной строки.

~/demo/batch-ops-dynamodb ❯ python delete_records_conditionally.py
Getting customer ids to delete
============
['199', '769', '873', '268', '289', '231', '272', '885', '729', '827']
Deleting customer 199
Deleting customer 769
Deleting customer 873
Deleting customer 268
Deleting customer 289
Deleting customer 231
Deleting customer 272
Deleting customer 885
Deleting customer 729
Deleting customer 827

Здорово! Скрипт работает как задумано. Теперь давайте проверим оставшиеся записи в нашей таблице.

~/demo/batch-ops-dynamodb ❯ aws dynamodb scan --endpoint-url http://localhost:8042 --table-name demo-customer-info
{
    "Items": [
        {
            "customerId": {
                "S": "22222"
            },
            "lastName": {
                "S": "smith"
            },
            "emailAddress": {
                "S": "[email protected]"
            }
        },
        {
            "customerId": {
                "S": "11111"
            },
            "lastName": {
                "S": "jones"
            },
            "emailAddress": {
                "S": "[email protected]"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

Остались только 2 записи, которые мы вставили «настоящие». Теперь мы можем быть уверены, что наш delete_records_conditionally.py скрипт делает то, для чего он предназначен.

Потрясающий материал. 👍

Некоторые подробности о скрипте удаления

Давайте взглянем на некоторые моменты из delete_records_conditionally.py скрипта, который мы только что написали.

Обратите внимание, что у нас есть deserializer, и мы используем его, чтобы получить customerId. Причина в том, что операция scan фактически возвращает что-то вроде этого.

# scan operation response
{'Items': [{'customerId': {'S': '300'}}, {'customerId': {'S': '794'}}, {'customerId': {'S': '266'}}, {'customerId': {'S': '281'}}, {'customerId': {'S': '223'}}, {'customerId': {'S': '660'}}, {'customerId': {'S': '384'}}, {'customerId': {'S': '673'}}, {'customerId': {'S': '378'}}, {'customerId': {'S': '426'}}], 'Count': 10, 'ScannedCount': 12, 'ResponseMetadata': {'RequestId': 'eb18e221-d825-4f28-b142-ff616d0ca323', 'HTTPStatusCode': 200, 'HTTPHeaders': {'content-type': 'application/x-amz-json-1.0', 'x-amz-crc32': '2155737492', 'x-amzn-requestid': 'eb18e221-d825-4f28-b142-ff616d0ca323', 'content-length': '310', 'server': 'Jetty(8.1.12.v20130726)'}, 'RetryAttempts': 0}}

Поскольку нас интересует только значение customerId, нам нужно десериализовать элемент DynamoDB, используя TypeDeserializer, который предоставляется библиотекой Boto3.

Еще один заслуживающий упоминания компонент - это Select и ProjectionExpression, которые являются параметрами функции scan. Эти 2 параметра работают рука об руку. Мы устанавливаем значение Select на SPECIFIC_ATTRIBUTES, и, согласно официальной документации Boto3, это вернет только атрибуты, перечисленные в AttributesToGet. AttributesToGet был помечен как устаревший параметр, и AWS рекомендует использовать вместо него ProjectionExpression.

Из официальной документации Boto3:

«Если вы используете параметр ProjectionExpression, то значение для Select может быть только SPECIFIC_ATTRIBUTES. Любое другое значение для Select вернет ошибку ».

Эти два параметра в основном говорят о том, что операция scan должна возвращать только атрибут customerId, что нам и нужно.

Наконец, мы используем функцию begins_with(), предоставляемую AWS. Эта функция принимает имя атрибута и префикс для проверки значения указанного атрибута.

Заворачивать

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

В любом случае, я надеюсь, что это руководство дало вам общее представление о том, как выполнять операции DynamoDB с использованием Python. Итак, будьте творческими, возьмите этот сценарий и улучшите его, чтобы делать более сложные вещи. Удачного взлома! 🙂

Примечание: вот репо на Github.