Итак, вам нужно надежное решение, быстрое реагирование на тысячи запросов в секунду, вы выбираете БД без SQL, и AWS DynamoDB - очаровательный принц: отличная производительность, неограниченное масштабирование и полное управление.

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

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

ммм ... очаровательный принц сейчас уже не так светит: - \

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

1. Запросы DynamoDB с использованием AWS Javascript SDK, знание ключей и индексов и запрос против сканирования

2. Как использовать простой синтаксис SQL для запроса DynamoDB и как подключиться к нему с помощью стандартного клиента MySQL.

3. Как запросить вашу DynamoDB, как если бы это была настоящая реляционная база данных (RDBMS):

  • Используйте функции агрегирования (количество, мин, макс…) с помощью группировать по
  • Таблицы JOIN и UNION DynamoDB
  • Межрегиональные запросы

Часть 1. Запрос DynamoDB с помощью AWS Javascript SDK, знание ключей и индексов, а также запрос и сканирование

Прежде чем выбрать, какой DynamoDB API использовать, вы должны хорошо знать ключи своей таблицы:

◦ Что такое первичный ключ и его ключ сортировки?

◦ Что такое индексы? Каков ключ сортировки каждого индекса?

После ответа на эти вопросы нам нужно решить, какой API использовать: Сканировать или Запрос:

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

Фильтрация Сканирование проста, вы можете использовать любой столбец (ключевой, сортировка, индекс, сортировка индекса или простой столбец) и иметь несколько фильтров в одном API.

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

Вот три практических правила:

◦ Запрос может содержать одно или два поля для фильтрации (но не более!)

◦ Одно из полей фильтра должно быть первичным ключом или индексным ключом.

◦ Второе поле фильтра должно быть связанным ключом сортировки.

Вот пример таблицы под названием testTable:

У него есть первичный ключ (id1) со связанным ключом сортировки (sort1),

и два индекса со своими собственными связанными ключами сортировки: id2 с sort2 и id3 с sort3. Id4 - это обычное поле.

вы можете увидеть первичный ключ и ключ сортировки на вкладке «Обзор» в консоли AWS.

Индексы и их рабочие ключи можно увидеть на вкладке индексов.

или вы можете получить эту информацию с помощью API describeTable SDK DynamoDB:

it("describe dynamo table", async () => {
    let table = "testTable";
    let params = {
        TableName: table
    };
    let [err,tableInfo] = await new Promise((resolved) => {
        let dynamodb = new AWS.DynamoDB({region: "us-west-1"});
        dynamodb.describeTable(params, function (err, data) {
            if (err) {
                console.log(`ERROR: ${err}`);
            }
            resolved([err, data]);
        });
    });
    console.log(tableInfo)
});

Вот результат:

Давайте посмотрим, какие фильтры запросов мы можем выполнить для этой таблицы:

  • Ключ (id1)
  • Ключ (id1) И sortKey (sort1)
  • Индекс1 (id2)
  • Индекс1 (id2) И index1sort (sort2)
  • Индекс2 (id3) И index2sort2 (sort3)

Но мы можем НЕ запрашивать:

  • SortKey (sort1) - ›без ключа
  • Index1sort (sort2) - ›сортировка без ключа индекса
  • Ключ И индекс (id1, id2) - ›может быть по ключу или индексу, но не по обоим
  • Ключ И index1sort (id1, sort2) - ›ключ может быть только с соответствующей сортировкой
  • Index1 AND index2sort (id2, sort3) - ›смешанный индекс и сортировка
  • Key1 AND sortKey AND index (id1, sort1, id2) - ›более 2 фильтров

Теперь, когда мы выбрали правильный API для нашего варианта использования, давайте посмотрим на реализацию с использованием AWS SDK для JavaScript:

  1. запрос с ключом и сортировкой:
// select id1, sort1, id2 from testTable where id1='a' and sort1='a'
// using dynamoDB query , key filter
it("dynamo basic query - key", async () => {
    this.AWS = require('aws-sdk');
    this.AWS.config.region = 'us-west-1';
    let dynamoDB = new AWS.DynamoDB();

    let table = "testTable";
    let totalRowsCount = 0;
    let totalRows = [];
    let [rows, keepWork] = [null,true];
    let limit = 1000;

    // for fields list
    let fields = "id1, sort1, id2";

    // for the query filter "where"
    let filterValues = {
        ':v1': {S: 'a'},
        ':v2': {S: 'a'},
    }
    // key filter(id1) is mandatory, sort1 is optional
    let KeyFilterExpression = 'id1 = :v1 and sort1 = :v2' 
    var params = {
        TableName: table,
        Limit: limit,
        ProjectionExpression: fields,        
        ExpressionAttributeValues: filterValues,  
        KeyConditionExpression: KeyFilterExpression   
    }

    while (keepWork) {   //loop to get all results (paging)
        [rows, keepWork] =  await new Promise((resolved) => {
            dynamoDB.query(params, (err, data) => {
                if (err) {
                    console.error(err);
                    throw err;
                }
                Array.prototype.push.apply(totalRows, data.Items)
                keepWork = data.LastEvaluatedKey;
                if (keepWork){
                  params.ExclusiveStartKey =   data.LastEvaluatedKey
                }
                resolved([data,keepWork])
            });
        })
        totalRowsCount+= rows.Items.length
    }
    console.log(totalRows)
})

2. запрос с индексом и сортировкой:

// select id1, sort1, id2 from testTable where id1='a' and sort1='a'
// using dyamoDB query, index filter
it("dynamo basic query - index", async () => {
    this.AWS = require('aws-sdk');
    this.AWS.config.region = 'us-west-1';
    let dynamoDB = new AWS.DynamoDB();

    let table = "testTable";
    let totalRowsCount = 0;
    let totalRows = [];
    let [rows, keepWork] = [null,true];
    let limit = 1000;

    // for fields list
    let fields = "id1, sort1, id2";

    // for the query filter "where"
    let filterValues = {                             
        ':v1': {S: 'a'},
        ':v2': {S: 'a'},
    }
// index key filter(id2) is mandatory, sort2 is optional    let IndexFilterExpression = 'id2 = :v1 and sort2 = :v2' 
    var params = {
        TableName: table,
        Limit: limit,
        IndexName: 'id2-sort2-index',               
        ProjectionExpression: fields,                
        ExpressionAttributeValues: filterValues,    
        KeyConditionExpression: IndexFilterExpression 
    }

    while (keepWork) {     //loop to get all results (paging)
        [rows, keepWork] =  await new Promise((resolved) => {
            dynamoDB.query(params, (err, data) => {
                if (err) {
                    console.error(err);
                    throw err;
                }
                Array.prototype.push.apply(totalRows, data.Items)
                keepWork = data.LastEvaluatedKey;
                if (keepWork){
                  params.ExclusiveStartKey =   data.LastEvaluatedKey
                }
                resolved([data,keepWork])
            });
        })
        totalRowsCount+= rows.Items.length
    }
    console.log(totalRows)
})

3. сканирование:

// select id1, sort1, id2 from testTable where id1='a' and sort1='a'
// using dynamoDB scan API
it("dynamo basic scan", async () => {
    this.AWS = require('aws-sdk');
    this.AWS.config.region = 'us-west-1';
    let dynamoDB = new AWS.DynamoDB();

    let table = "testTable";
    let totalRowsCount = 0;
    let totalRows = [];
    let [rows, keepWork] = [null,true];
    let limit = 1000;

    // for fields list
    let fields = "id1, sort1, id2";

    // for the query filter "where"

    let filterValues = {      
        ':v1': {S: 'a'},
        ':v2': {S: 'a'},
    }
    let FilterExpression = 'id1 = :v1 and sort1 = :v2'

    var params = {
        TableName: table,
        Limit: limit,
        ProjectionExpression: fields,        
        ExpressionAttributeValues: filterValues,  
        FilterExpression: FilterExpression     
    }

    while (keepWork) {       //loop to get all results (paging)
        [rows, keepWork] =  await new Promise((resolved) => {
            dynamoDB.scan(params, (err, data) => {
                if (err) {
                    console.error(err);
                    throw err;
                }
                Array.prototype.push.apply(totalRows, data.Items)
                keepWork = data.LastEvaluatedKey;
                if (keepWork){
                  params.ExclusiveStartKey =   data.LastEvaluatedKey
                }
                resolved([data,keepWork])
            });
        })
        totalRowsCount+= rows.Items.length
    }
    console.log(totalRows)
})

Три длинные функции, показанные выше, представляют собой громоздкий способ DynamoDB выполнять следующие три простых SQL-запроса:

  • выберите id1, sort1, id2 из testTable, где id1 = ‘a’ и sort1 = ‘a’
  • выберите id1, sort1, id2 из testTable, где id2 = ‘a’ и sort2 = ‘a’
  • выберите id1, sort1, id2 из testTable, где id4 = ‘a’ и sort1 = ‘a’

Заключение:

Dynamo - отличное решение noSQL, но логика ключей / индексов не была бы такой тривиальной, если бы вы могли использовать SQL. API слишком длинные и сложные.

Мой список желаний от DynamoDB следующий:

1. Упростите API с помощью синтаксиса SQL.

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

3. Разрешить все функции реляционной БД:

а. Функции агрегирования, такие как количество, мин, макс…, сгруппировать по и по…

б. Разрешите ПРИСОЕДИНЯТЬ две разные таблицы DynamoDB в регионах и между ними.

c. Разрешить ОБЪЕДИНЕНИЕ результатов запроса из разных таблиц DynamoDB и регионов

г. Подключитесь к DynamoDB с помощью стандартного клиента MySQL

Вы не поверите, все это возможно! Но это для Частей II и III этой серии.

Не могу дождаться? Хотите подсказку? superQuery творит чудеса!

Будьте на связи!