Статьи

Включение шардов для существующей базы данных

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

Сценарий таков, что у нас очень успешное приложение, и мы начинаем чувствовать необходимость переносить данные в несколько сегментов. В настоящее время все данные находятся на сервере RVN1. Мы хотим добавить RVN2 и RVN3 к смеси. В этом посте мы предполагаем, что у нас есть понятие «Клиенты и счета».

Ранее мы обращались к базе данных, используя простое хранилище документов:

var documentStore = new DocumentStore
{
Url = "http://RVN1:8080",
DefaultDatabase = "Shop"
};

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

var shards = new Dictionary<string, IDocumentStore>
{
{"Origin", new DocumentStore {Url = "http://RVN1:8080", DefaultDatabase = "Shop"}},//existing data
{"ME", new DocumentStore {Url = "http://RVN2:8080", DefaultDatabase = "Shop_ME"}},
{"US", new DocumentStore {Url = "http://RVN3:8080", DefaultDatabase = "Shop_US"}},
};

var shardStrategy = new ShardStrategy(shards)
.ShardingOn<Customer>(c => c.Region)
.ShardingOn<Invoice> (i => i.Customer);

var documentStore = new ShardedDocumentStore(shardStrategy).Initialize();

Это на самом деле не сработает. Нам придется сделать немного больше. Начнем с того, что происходит, когда у нас нет совпадения 1: 1 между регионом и шардом? Именно тогда переводчик становится актуальным:

.ShardingOn<Customer>(c => c.Region, region =>
{
    switch (region)
    {
        case "Middle East":
            return "ME";
        case "USA":
        case "United States":
        case "US":
            return "US";
        default:
            return "Origin";
    }
})

Мы в основном говорим, что отображаем несколько значений в один регион. Но этого недостаточно. Вновь сохраненные документы будут иметь префикс шарда, поэтому сохранение нового клиента и счета в шарде США будет отображаться как:

образ

Но в существующих данных этого нет (создано без шардинга).

образ

Поэтому нам нужно приложить дополнительные усилия, чтобы сообщить RavenDB о них. Мы делаем это, используя следующие две функции:

 Func<string, string> potentialShardToShardId = val =>
 {
     var start = val.IndexOf('/');
     if (start == -1)
         return val;
     var potentialShardId = val.Substring(0, start);
     if (shards.ContainsKey(potentialShardId))
         return potentialShardId;
     // this is probably an old id, let us use it.
     return "Origin";

 };
 Func<string, string> regionToShardId = region =>
 {
     switch (region)
     {
         case "Middle East":
             return "ME";
         case "USA":
         case "United States":
         case "US":
             return "US";
         default:
             return "Origin";
     }
 };

Затем мы можем зарегистрировать нашу конфигурацию сегментирования так:

  var shardStrategy = new ShardStrategy(shards)
      .ShardingOn<Customer, string>(c => c.Region, potentialShardToShardId, regionToShardId)
      .ShardingOn<Invoice, string>(x => x.Customer, potentialShardToShardId, regionToShardId); 

Это позволяет обрабатывать как новые, так и старые идентификаторы, и позволяет RavenDB понять, как выполнять запросы оптимальным образом. Например, запрос по всем счетам для ‘Customers / 1’ будет попадать только на сервер RVN1.

Тем не менее, мы еще не закончили. Новые клиенты, которые не принадлежат к Ближнему Востоку или США, все равно перейдут на старый сервер, и мы не хотим никаких изменений идентификатора там. Мы можем сказать RavenDB, как с этим обращаться:

var defaultModifyDocumentId = shardStrategy.ModifyDocumentId;
shardStrategy.ModifyDocumentId = (convention, shardId, documentId) =>
{
    if(shardId == "Origin")
        return documentId;

    return defaultModifyDocumentId(convention, shardId, documentId);
};

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

 var documentStore = new ShardedDocumentStore(shardStrategy);
 documentStore.RegisterListener(new AddShardIdToMetadataStoreListener());
 documentStore.Initialize();

Где слушатель выглядит так:

 public class AddShardIdToMetadataStoreListener : IDocumentStoreListener
 {
     public bool BeforeStore(string key, object entityInstance, RavenJObject metadata, RavenJObject original)
     {
         if (metadata.ContainsKey(Constants.RavenShardId) == false)
         {
             metadata[Constants.RavenShardId] = "Origin";// the default shard id
         }
         return false;
     }

     public void AfterStore(string key, object entityInstance, RavenJObject metadata)
     {
     }
 }

И это все. Я знаю, что здесь, кажется, происходит довольно много, но в основном это можно разбить на три действия, которые мы предпринимаем:

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

И это в значительной степени это.