В предыдущем модуле осуществлялось добавление инвертированного индекса к таблице. Инвертированный индекс используется для добавления дополнительных шаблонов запроса для реляционных данных. Но мы увидели, что одна проблема все же осталась. Хотя можно было отправить запрос ко всем сущностям Friendship для определенной сущности User, в сущности Friendship отсутствовала информация об отслеживаемом пользователе.

Этот модуль посвящен частичной нормализации.

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

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

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

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

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

Время, необходимое для прохождения модуля: 20 минут


  • Шаг 1. Использование частичной нормализации для поиска отслеживаемых пользователей

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

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

    В скачанном коде в каталоге application/ есть файл под названием find_and_enrich_following_for_user.py. В скрипте указывается приведенное ниже содержимое.

    import boto3
    
    from entities import User
    
    dynamodb = boto3.client('dynamodb')
    
    USERNAME = "haroldwatkins"
    
    
    def find_and_enrich_following_for_user(username):
        friend_value = "#FRIEND#{}".format(username)
        resp = dynamodb.query(
            TableName='quick-photos',
            IndexName='InvertedIndex',
            KeyConditionExpression="SK = :sk",
            ExpressionAttributeValues={":sk": {"S": friend_value}},
            ScanIndexForward=True
        )
    
        keys = [
            {
                "PK": {"S": "USER#{}".format(item["followedUser"]["S"])},
                "SK": {"S": "#METADATA#{}".format(item["followedUser"]["S"])},
            }
            for item in resp["Items"]
        ]
    
        friends = dynamodb.batch_get_item(
            RequestItems={
                "quick-photos": {
                    "Keys": keys
                }
            }
        )
    
        enriched_friends =  [User(item) for item in friends['Responses']['quick-photos']]
    
        return enriched_friends
    
    
    
    follows = find_and_enrich_following_for_user(USERNAME)
    
    print("Users followed by {}:".format(USERNAME))
    for follow in follows:
        print(follow)

    Функция find_and_enrich_following_for_user аналогична функции find_follower_for_user, примененной в последнем модуле. Функция принимает имя пользователя, для которого необходимо найти отслеживаемых пользователей. Сначала функция создает запрос Query с использованием инвертированного индекса для поиска всех пользователей, на которых подписан пользователь с указанным именем. Затем выполняется сбор BatchGetItem для получения данных о всей сущности User для каждого из отслеживаемых пользователей с последующим возвратом этих сущностей.

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

    Выполните скрипт, запустив указанную ниже команду в терминале.

    python application/find_and_enrich_following_for_user.py

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

    Users followed by haroldwatkins:
    User<ppierce -- Ernest Mccarty>
    User<vpadilla -- Jonathan Scott>
    User<david25 -- Abigail Alvarez>
    User<jacksonjason -- John Perry>
    User<chasevang -- Leah Miller>
    User<frankhall -- Stephanie Fisher>
    User<nmitchell -- Amanda Green>
    User<tmartinez -- Kristin Stevens>
    User<natasha87 -- Walter Carlson>
    User<geoffrey32 -- Mary Martin>
    

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

  • Выводы

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

    В следующем модуле по мере добавления реакции на фотографию или подписки на пользователя будут применяться транзакции DynamoDB.