Запрос DynamoDB по дате

103

Я исхожу из фона реляционной базы данных и пытаюсь работать с DynamoDB от Amazon

У меня есть таблица с хэш-ключом «DataID» и диапазоном «CreatedAt» и кучей элементов в ней.

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

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

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

платок
источник

Ответы:

34

Обновленный ответ:

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

Для варианта использования в этом вопросе вы можете использовать глобальный вторичный индекс в поле «CreatedAt».

Подробнее о вторичных индексах DynamoDB см. В документации по вторичному индексу.

Оригинальный ответ:

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

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

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

Майк Брант
источник
14
См. Комментарии к ответу ниже; сейчас нет способов справиться с этим, по крайней мере, не для того, что попросил OP. GSI по-прежнему требуют, чтобы вы указали хэш-ключ, поэтому вы не можете запрашивать все записи с CreatedAtточкой больше определенной.
pkaeding
4
@pkaeding прав. Вы можете получить записи старше определенной даты с помощью сканирования , но вы не можете получить их в отсортированном порядке. GSI в этом случае вам не поможет. Невозможно отсортировать ключ раздела или запросить только ключ диапазона .
gkiko
15
Для тех из вас, кто запутался. ЭТОТ ОТВЕТ НЕПРАВИЛЬНЫЙ. Его первоначальный ответ правильный, но его обновленный ответ - нет. Прочтите ответ Уоррена Парада ниже. Это верно.
Райан Шиллингтон
1
@MikeBrant Я хочу запросить (а не сканировать, который просматривает каждый элемент в таблице, что делает его очень неэффективным и дорогостоящим) таблицу на хеш-ключе GSI таблицы (CreatedAt), используя символ «больше». Насколько я знаю, это невозможно.
Азиз Джавед
4
Проблема, с которой вы, вероятно, столкнетесь при использовании даты в качестве основного раздела, заключается в том, что вы можете создать точку доступа на некоторых или на одном из одноранговых узлов из-за того, что в большинстве хранилищ данных новые данные запрашиваются чаще, чем старые.
Knowledge
53

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

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

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

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

Лучше всего определить более полезные разделы, которые нужно создать для сохранения данных:

  • Вам действительно нужно просматривать все строки, или это только строки конкретного пользователя?

  • Можно ли сначала сузить список по месяцам и выполнить несколько запросов (по одному на каждый месяц)? Или по году?

  • Если вы выполняете анализ временных рядов, есть несколько вариантов: изменить ключ раздела на что-то вычисленное, PUTчтобы упростить queryзадачу, или использовать другой продукт aws, например kinesis, который позволяет вести журнал только с добавлением.

Уоррен Парад
источник
4
Я хочу выделить вариант, который вы предложили в последнем абзаце о рассмотрении «по годам». Создайте такой атрибут, как yyyyи хэш, но также создайте createdдату, которую вы можете использовать в качестве ключа диапазона. Затем вы получаете 10 ГБ данных в год (27 МБ в день), что, вероятно, будет достаточно для других обстоятельств. Это означает, что вам нужно создавать запрос в год, когда запросы даты выходят за границу года, но, по крайней мере, это будет работать и безопаснее, чем создание фиктивного хеш-ключа.
Райан Шиллингтон
1
Другой вариант: stackoverflow.com/questions/35963243/…
Райан Шиллингтон
1
Как поясняется в приведенной выше ссылке, ключи разделов строго по времени могут привести к возникновению горячих точек. если вы должны использовать ключи раздела, основанные на времени, лучше добавить какой-либо другой элемент к ключу раздела, чтобы распределить период времени по нескольким разделам. Я видел предложения просто использовать префикс между 0-n, где n - количество разделов, по которым каждый раз должен быть распределен сегмент.
dres
@RyanShillington Не существует ограничения в 10 ГБ для глобальных вторичных индексов. Это ограничение применяется только к локальным вторичным индексам.
Саймон Форсберг
18

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

Hash Key                 | Range Key
------------------------------------
Date value of CreatedAt  | CreatedAt

На пользователя HTTP API накладывается ограничение на указание количества дней для извлечения данных, по умолчанию - 24 часа.

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

Гиреш
источник
8

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

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

|  ID  | DataID | Created | Data |
|------+--------+---------+------|
| hash | xxxxx  | 1234567 | blah |

Ваш хеш-ключ - это идентификатор. Ваш вторичный индекс определяется как: DataID-Created-index (это имя, которое будет использовать DynamoDB)

Затем вы можете сделать такой запрос:

var params = {
    TableName: "Table",
    IndexName: "DataID-Created-index",
    KeyConditionExpression: "DataID = :v_ID AND Created > :v_created",
    ExpressionAttributeValues: {":v_ID": {S: "some_id"},
                                ":v_created": {N: "timestamp"}
    },
    ProjectionExpression: "ID, DataID, Created, Data"
};

ddb.query(params, function(err, data) {
    if (err) 
        console.log(err);
    else {
        data.Items.sort(function(a, b) {
            return parseFloat(a.Created.N) - parseFloat(b.Created.N);
        });
        // More code here
    }
});

По сути, ваш запрос выглядит так:

SELECT * FROM TABLE WHERE DataID = "some_id" AND Created > timestamp;

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

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

ET
источник
1
Вы говорите, что ограничений нет, но вы должны знать, что этот подход означает, что вы можете сохранить не более 10 ГБ данных (максимум для одного раздела).
Райан Шиллингтон
Это был бы подход, если бы известен DataID. Но здесь нам нужно получить каждую строку, для которой создано больше, чем некоторая дата.
Ясит Прабуддхака
3

Вы можете сделать ключ Hash чем-то вроде идентификатора «категории продукта», а затем ключ диапазона как комбинацию отметки времени с уникальным идентификатором, добавленным в конце. Таким образом, вы знаете хэш-ключ и можете запросить дату с числом больше.

Грег
источник
1

У вас может быть несколько одинаковых хеш-ключей; но только если у вас есть ключ диапазона, который меняется. Думайте об этом как о форматах файлов; вы можете иметь 2 файла с одинаковыми именами в одной папке, если их формат различается. Если их формат такой же, их имена должны быть разными. Та же концепция применима к ключам хэша / диапазона DynamoDB; просто воспринимайте хэш как имя, а диапазон - как формат.

Кроме того, я не помню, были ли они у них во время OP (я не верю, что они были), но теперь они предлагают локальные вторичные индексы.

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

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

Дополнительные данные о вторичных индексах можно найти в документации Amazon DynamoDB здесь. для заинтересованных.

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

Д.Гольберг
источник
Я попытался создать таблицу DynamoDB, в которой был AWSDynamoDBKeySchemaElement 'createdAt' типа hash и снова AWSDynamoDBKeySchemaElement 'createdAt' диапазона типов, и я получил сообщение об ошибке, в котором говорилось, что Error Domain = com.amazonaws.AWSDynamoDBErrorDomain Code = 0 (nullIn) = {__ type = com.amazon.coral.validate # ValidationException, message = Хэш-ключ и элемент Range Key в KeySchema имеют одно и то же имя}. Поэтому я не думаю, что вы говорите правильно.
user1709076 03
Я считаю, что вы неправильно поняли (хотя, полагаю, я тоже не очень ясно изложил свое описание). У вас не может быть двух разных атрибутов (столбцов) с одним и тем же именем в таблице, но когда вы создаете хэш-ключ с ключом диапазона, у вас может быть несколько элементов, которые все используют один и тот же хэш, если их диапазон отличается, и наоборот. Например: ваш хэш - «ID», а диапазон - «Дата», у вас может быть 2 экземпляра идентификатора «1234», если их дата отличается.
DGolberg
Ах, Д.Гольдберг! Я понимаю тебя сейчас. Замечательно. Так что в моем случае, поскольку я только и всегда буду просто запрашивать текстовые сообщения «after date = x», похоже, я мог бы установить для всех текстовых сообщений одно и то же «fake_hash = 1». Затем выполните мои query.keyConditionExpression = @ "fake_hash = 1 и #Date>: val". Большое спасибо. Если у вас есть какие-либо другие данные, я был бы рад их услышать, потому что кажется странным иметь хэш, который всегда имеет одно и то же значение?
user1709076 05
Мне пришлось бы проверить еще раз, но я почти уверен, что вы можете выполнить запрос только по хеш-таблицам ... хотя, если вы используете метку даты / времени в качестве хеша, я бы рекомендовал записывать до кратчайшая возможная единица, например миллисекунды или нано / микросекунды (независимо от наименьшей единицы времени, которую может записать код), чтобы уменьшить вероятность перекрытия даты / времени. Кроме того, вы можете добавить оптимистичную блокировку, чтобы еще больше снизить вероятность перекрытия: docs.aws.amazon.com/amazondynamodb/latest/developerguide/… Просто повторите попытку в другой раз, если возникнет конфликт.
DGolberg
-11

Обновленный ответ Нет удобного способа сделать это с помощью Dynamo DB Queries с предсказуемой пропускной способностью. Один (неоптимальный) вариант - использовать GSI с искусственным HashKey и CreatedAt. Затем запросите только HashKey и укажите ScanIndexForward, чтобы упорядочить результаты. Если вы можете придумать естественный HashKey (например, категорию элемента и т. Д.), То этот метод является победителем. С другой стороны, если вы сохраните один и тот же HashKey для всех элементов, это повлияет на пропускную способность в основном, когда ваш набор данных вырастет за пределы 10 ГБ (один раздел).

Исходный ответ: теперь это можно сделать в DynamoDB с помощью GSI. Сделайте поле «CreatedAt» как GSI и выполните такие запросы, как (GT some_date). Сохраните дату в виде числа (мсек с эпохи) для такого рода запросов.

Подробности доступны здесь: Глобальные вторичные индексы - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

Это очень мощная функция. Имейте в виду, что запрос ограничен (EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN) Условием - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html

Sony Kadavan
источник
31
Я проголосовал против, потому что, насколько я могу судить, ваш ответ неверен. Подобно первичному ключу таблицы, вы можете запросить хэш-ключ GSI только с помощью оператора EQ. Если вы подразумевали, что это CreatedAtдолжен быть ключ диапазона GSI, тогда вам нужно будет выбрать хэш-ключ - и тогда вы вернетесь туда, откуда начали, потому что вы сможете запрашивать GT CreatedAtтолько для определенного значения хэш-ключ.
PaF
Согласен с PaF. Использование GSI с хеш-ключом в качестве времени создания не помогает с вопросом, заданным в OP.
4-8-15-16-23-42,