Поделиться через


Перенос приложения из Amazon DynamoDB в Azure Cosmos DB

ОБЛАСТЬ ПРИМЕНЕНИЯ: NoSQL

Azure Cosmos DB — это масштабируемая, глобально распределенная, полностью управляемая база данных. Он обеспечивает гарантированный доступ к данным с низкой задержкой.

В этой статье описывается перенос приложения .NET из Amazon DynamoDB в Azure Cosmos DB с минимальными изменениями кода. Дополнительные сведения об Azure Cosmos DB см. в этой обзорной статье.

Концептуальные различия

В следующей таблице перечислены основные основные различия между Azure Cosmos DB и DynamoDB:

DynamoDB Azure Cosmos DB
Неприменимо База данных
Таблица Коллекция
Товар Документ
Атрибут Поле
Младший индекс Младший индекс
Первичный ключ > ключ секции Ключ партиционирования
Первичный ключ > ключ сортировки Не требуется
Поток Лента изменений
Записать вычислительный блок Единица запроса (гибкая, можно использовать для операций чтения или записи)
Чтение вычислительного блока Единица запроса (гибкая, можно использовать для операций чтения или записи)
Глобальная таблица Необязательно. Вы можете напрямую выбрать регион при подготовке учетной записи Azure Cosmos DB. (Вы можете изменить регион позже.)

Структурные различия

Структура JSON Azure Cosmos DB проще, чем структура JSON в DynamoDB. В следующем примере показаны различия.

DynamoDB

Следующий объект JSON представляет формат данных в DynamoDB:

{
TableName: "Music",
KeySchema: [
{ 
  AttributeName: "Artist",
  KeyType: "HASH", //Partition key
},
{ 
  AttributeName: "SongTitle",
  KeyType: "RANGE" //Sort key
}
],
AttributeDefinitions: [
{ 
  AttributeName: "Artist",
  AttributeType: "S"
},
{ 
  AttributeName: "SongTitle",
  AttributeType: "S"
}
],
ProvisionedThroughput: {
  ReadCapacityUnits: 1,
  WriteCapacityUnits: 1
 }
}

Azure Cosmos DB

Следующий объект JSON представляет формат данных в Azure Cosmos DB:

{
"Artist": "",
"SongTitle": "",
"AlbumTitle": "",
"Year": 9999,
"Price": 0.0,
"Genre": "",
"Tags": ""
}

Перенос кода

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

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

git clone https://github.com/Azure-Samples/DynamoDB-to-CosmosDB

Предпосылки

  • .NET Framework 4.7.2.
  • Последняя версия Visual Studio с рабочей нагрузкой разработки Azure. Вы можете начать с бесплатной среды разработки Visual Studio Community. При установке Visual Studio включите рабочую нагрузку разработки для Azure.
  • Доступ к учетной записи Azure Cosmos DB для NoSQL.
  • Локальная установка Amazon DynamoDB.
  • Java 8.
  • Скачиваемая версия Amazon DynamoDB. Запустите его через порт 8000. (Вы можете изменить и настроить код.)

Настройка кода

Добавьте в проект следующий пакет NuGet:

Install-Package Microsoft.Azure.Cosmos

Установка подключения

DynamoDB

В Amazon DynamoDB используется следующий код для подключения:

    AmazonDynamoDBConfig addbConfig = new AmazonDynamoDBConfig() ;
        addbConfig.ServiceURL = "endpoint";
        try { aws_dynamodbclient = new AmazonDynamoDBClient( addbConfig ); }

Azure Cosmos DB

Для подключения к Azure Cosmos DB измените код следующим образом.

client_documentDB = new CosmosClient(
    "<nosql-account-endpoint>",
    tokenCredential
);

Оптимизация подключения в Azure Cosmos DB

В Azure Cosmos DB можно оптимизировать подключение с помощью следующих параметров.

Создание контейнера

DynamoDB

Чтобы сохранить данные в Amazon DynamoDB, сначала необходимо создать таблицу. Определите схему, тип ключа и атрибуты, как показано в следующем коде:

// movies_key_schema
public static List<KeySchemaElement> movies_key_schema
  = new List<KeySchemaElement>
{
  new KeySchemaElement
  {
    AttributeName = partition_key_name,
    KeyType = "HASH"
  },
  new KeySchemaElement
  {
    AttributeName = sort_key_name,
    KeyType = "RANGE"
  }
};

// key names for the Movies table
public const string partition_key_name = "year";
public const string sort_key_name      = "title";
  public const int readUnits=1, writeUnits=1; 

    // movie_items_attributes
    public static List<AttributeDefinition> movie_items_attributes
  = new List<AttributeDefinition>
{
  new AttributeDefinition
  {
    AttributeName = partition_key_name,
    AttributeType = "N"
  },
  new AttributeDefinition
  {
    AttributeName = sort_key_name,
    AttributeType = "S"
  }

CreateTableRequest  request;
CreateTableResponse response;

// Build the 'CreateTableRequest' structure for the new table
request = new CreateTableRequest
{
  TableName             = table_name,
  AttributeDefinitions  = table_attributes,
  KeySchema             = table_key_schema,
  // Provisioned-throughput settings are always required,
  // although the local test version of DynamoDB ignores them.
  ProvisionedThroughput = new ProvisionedThroughput( readUnits, writeUnits );
};

Azure Cosmos DB

В Amazon DynamoDB необходимо подготовить единицы вычислений чтения и вычислительные единицы записи. В Azure Cosmos DB укажите пропускную способность в виде единиц запросов в секунду (ЕЗ/с). Вы можете динамически использовать RU/s для выполнения любых операций. Данные организованы как база данных, контейнер и элемент. Пропускную способность можно указать на уровне базы данных, на уровне коллекции или обоих.

Создание базы данных:

await client_cosmosDB.CreateDatabaseIfNotExistsAsync(movies_table_name);

Чтобы создать контейнер, выполните приведенные действия.

await cosmosDatabase.CreateContainerIfNotExistsAsync(new ContainerProperties() { PartitionKeyPath = "/" + partitionKey, Id = new_collection_name }, provisionedThroughput);

Загрузка данных

DynamoDB

В приведенном ниже коде показано, как необходимо загружать данные в Amazon DynamoDB. В коде moviesArray перечислены документы JSON, а затем необходимо выполнить итерацию и загрузить документы JSON в Amazon DynamoDB.

int n = moviesArray.Count;
for( int i = 0, j = 99; i < n; i++ )
    {
  try
  {
    string itemJson = moviesArray[i].ToString();
    Document doc = Document.FromJson(itemJson);
    Task putItem = moviesTable.PutItemAsync(doc);
    if( i >= j )
    {
      j++;
      Console.Write( "{0,5:#,##0}, ", j );
      if( j % 1000 == 0 )
        Console.Write( "\n " );
      j += 99;
    }
    await putItem;

Azure Cosmos DB

В Azure Cosmos DB вы можете выполнить потоковую передачу и запись с помощью moviesContainer.CreateItemStreamAsync(). Однако в этом примере JSON десериализуется в тип MovieModel, чтобы продемонстрировать возможность приведения типов. Код многопоточный и использует распределенную архитектуру в Azure Cosmos DB для ускорения загрузки.

List<Task> concurrentTasks = new List<Task>();
for (int i = 0, j = 99; i < n; i++)
{
  try
  {
      MovieModel doc= JsonConvert.DeserializeObject<MovieModel>(moviesArray[i].ToString());
      doc.Id = Guid.NewGuid().ToString();
      concurrentTasks.Add(moviesContainer.CreateItemAsync(doc,new PartitionKey(doc.Year)));
      {
          j++;
          Console.Write("{0,5:#,##0}, ", j);
          if (j % 1000 == 0)
              Console.Write("\n               ");
          j += 99;
      }
      
  }
  catch (Exception ex)
  {
      Console.WriteLine("\n     ERROR: Could not write the movie record #{0:#,##0}, because:\n       {1}",
                          i, ex.Message);
      operationFailed = true;
      break;
  }
}
await Task.WhenAll(concurrentTasks);

Создание документа

DynamoDB

Написание нового документа в Amazon DynamoDB не является безопасным. В следующем примере используется newItem тип документа:

Task<Document> writeNew = moviesTable.PutItemAsync(newItem, token);
await writeNew;

Azure Cosmos DB

Azure Cosmos DB обеспечивает безопасность типов с помощью модели данных. В этом примере используется модель данных с именем MovieModel:

public class MovieModel
{
    [JsonProperty("id")]
    public string Id { get; set; }
    [JsonProperty("title")]
    public string Title{ get; set; }
    [JsonProperty("year")]
    public int Year { get; set; }
    public MovieModel(string title, int year)
    {
        this.Title = title;
        this.Year = year;
    }
    public MovieModel()
    {

    }
    [JsonProperty("info")]
    public   MovieInfo MovieInfo { get; set; }

    internal string PrintInfo()
    {
        if(this.MovieInfo!=null)
        return            string.Format("\nMovie with title:{1}\n Year: {2}, Actors: {3}\n Directors:{4}\n Rating:{5}\n", this.Id, this.Title, this.Year, String.Join(",",this.MovieInfo.Actors), this.MovieInfo, this.MovieInfo.Rating);
        else
            return string.Format("\nMovie with  title:{0}\n Year: {1}\n",  this.Title, this.Year);
    }
}

В Azure Cosmos DB newItem это MovieModel:

 MovieModel movieModel = new MovieModel()
            {
                Id = Guid.NewGuid().ToString(),
                Title = "The Big New Movie",
                Year = 2018,
                MovieInfo = new MovieInfo() { Plot = "Nothing happens at all.", Rating = 0 }
            };
    var writeNew= moviesContainer.CreateItemAsync(movieModel, new Microsoft.Azure.Cosmos.PartitionKey(movieModel.Year));
    await writeNew;

Чтение документа

DynamoDB

Для чтения данных в Amazon DynamoDB необходимо определить примитивы.

// Create primitives for the HASH and RANGE portions of the primary key
Primitive hash = new Primitive(year.ToString(), true);
Primitive range = new Primitive(title, false);

  Task<Document> readMovie = moviesTable.GetItemAsync(hash, range, token);
  movie_record = await readMovie;

Azure Cosmos DB

При использовании Azure Cosmos DB запрос является естественным (LINQ):

IQueryable<MovieModel> movieQuery = moviesContainer.GetItemLinqQueryable<MovieModel>(true)
                        .Where(f => f.Year == year && f.Title == title);
// The query is executed synchronously here, but can also be executed asynchronously via the IDocumentQuery<T> interface
    foreach (MovieModel movie in movieQuery)
    {
      movie_record_cosmosdb = movie;
    }

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

Обновление элемента

DynamoDB

Обновление элемента в Amazon DynamoDB:

updateResponse = await client.UpdateItemAsync( updateRequest );

Azure Cosmos DB

В Azure Cosmos DB обновление обрабатывается как Upsert операция (т. е. вставьте документ, если он не существует):

await moviesContainer.UpsertItemAsync<MovieModel>(updatedMovieModel);

Удаление документа

DynamoDB

Чтобы удалить элемент в Amazon DynamoDB, необходимо еще раз обратиться к примитивам.

Primitive hash = new Primitive(year.ToString(), true);
      Primitive range = new Primitive(title, false);
      DeleteItemOperationConfig deleteConfig = new DeleteItemOperationConfig( );
      deleteConfig.ConditionalExpression = condition;
      deleteConfig.ReturnValues = ReturnValues.AllOldAttributes;
      
  Task<Document> delItem = table.DeleteItemAsync( hash, range, deleteConfig );
        deletedItem = await delItem;

Azure Cosmos DB

В Azure Cosmos DB вы можете получить документ и удалить его асинхронно:

var result= ReadingMovieItem_async_List_CosmosDB("select * from c where c.info.rating>7 AND c.year=2018 AND c.title='The Big New Movie'");
while (result.HasMoreResults)
{
  var resultModel = await result.ReadNextAsync();
  foreach (var movie in resultModel.ToList<MovieModel>())
  {
    await moviesContainer.DeleteItemAsync<MovieModel>(movie.Id, new PartitionKey(movie.Year));
  }
  }

Поиск документов

DynamoDB

В Amazon DynamoDB функции API необходимы для запроса данных:

QueryOperationConfig config = new QueryOperationConfig( );
  config.Filter = new QueryFilter( );
  config.Filter.AddCondition( "year", QueryOperator.Equal, new DynamoDBEntry[ ] { 1992 } );
  config.Filter.AddCondition( "title", QueryOperator.Between, new DynamoDBEntry[ ] { "B", "Hzz" } );
  config.AttributesToGet = new List<string> { "year", "title", "info" };
  config.Select = SelectValues.SpecificAttributes;
  search = moviesTable.Query( config ); 

Azure Cosmos DB

В Azure Cosmos DB можно выполнять проекцию и фильтрацию в простом SQL-запросе:

var result = moviesContainer.GetItemQueryIterator<MovieModel>( 
  "select c.Year, c.Title, c.info from c where Year=1998 AND (CONTAINS(Title,'B') OR CONTAINS(Title,'Hzz'))");

Для операций с диапазоном (например, betweenнеобходимо выполнить сканирование в Amazon DynamoDB:

ScanRequest sRequest = new ScanRequest
{
  TableName = "Movies",
  ExpressionAttributeNames = new Dictionary<string, string>
  {
    { "#yr", "year" }
  },
  ExpressionAttributeValues = new Dictionary<string, AttributeValue>
  {
      { ":y_a", new AttributeValue { N = "1960" } },
      { ":y_z", new AttributeValue { N = "1969" } },
  },
  FilterExpression = "#yr between :y_a and :y_z",
  ProjectionExpression = "#yr, title, info.actors[0], info.directors, info.running_time_secs"
};

ClientScanning_async( sRequest ).Wait( );

В Azure Cosmos DB можно использовать SQL-запрос и однострочный оператор:

var result = moviesContainer.GetItemQueryIterator<MovieModel>( 
  "select c.title, c.info.actors[0], c.info.directors,c.info.running_time_secs from c where BETWEEN year 1960 AND 1969");

Удаление контейнера

DynamoDB

Для удаления таблицы в Amazon DynamoDB можно указать следующую инструкцию.

client.DeleteTableAsync( tableName );

Azure Cosmos DB

Для удаления коллекции в Azure Cosmos DB укажите следующую инструкцию.

await moviesContainer.DeleteContainerAsync();

При необходимости удалите базу данных:

await cosmosDatabase.DeleteAsync();

Сводка

Как показано в предыдущих примерах, Azure Cosmos DB поддерживает естественные запросы (SQL), а операции являются асинхронными. Вы можете легко перенести сложный код в Azure Cosmos DB. Код становится проще после миграции.