В списке рассылки возник вопрос, как мы можем включить шардинг для существующей базы данных. Я расскажу о переносе данных в этом сценарии позже.
Сценарий таков, что у нас очень успешное приложение, и мы начинаем чувствовать необходимость переносить данные в несколько сегментов. В настоящее время все данные находятся на сервере 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)
{
}
}
И это все. Я знаю, что здесь, кажется, происходит довольно много, но в основном это можно разбить на три действия, которые мы предпринимаем:
- Измените существующие метаданные, добавив идентификатор сервера сегментирования через прослушиватель.
- Измените соглашение об идентификаторе документа, чтобы документы на старом сервере не имели обозначения (необязательно).
- Измените конфигурацию сегментирования, чтобы мы понимали, что документы без префикса сегмента на самом деле принадлежат сегменту происхождения.
И это в значительной степени это.