Тази страница преминава през типичен пример, за да покаже колко безболезнени могат да бъдат типичните миграции на данни, когато използвате Redis и други хранилища за данни без схеми за NoSQL.
Всички страници на приложения в блога на Redis #
- Проектиране на NoSQL база данни с помощта на Redis
- Безболезнени миграции на данни с помощта на Redis и други NoSQL хранилища за данни без схеми
Безболезнени миграции на данни с NoSQL хранилища за данни без схеми и Redis #
Разработване на ново Системите за бази данни на зелено, използващи бекенд RDBMS, са предимно безпроблемно изживяване. Преди системата да заработи, вие можете лесно да модифицирате схема, като изтриете цялата база данни на приложението и я създадете отново с автоматизирани DDL скриптове, които ще я създадат и попълват с тестови данни, които отговарят на новата ви схема.
Истинските проблеми във вашия ИТ живот се случват след първото ви внедряване и вашата система започва да работи. В този момент вече нямате опцията да разрушите базата данни и да я създадете отново от нулата. Ако имате късмет, имате скрипт, който може автоматично да изведе необходимите DDL изрази, за да мигрира от старата ви схема към новата ви. Въпреки това всички значителни промени във вашата схема вероятно ще включват късни нощи, престой и нетривиално количество усилия, за да се гарантира успешна миграция към новата db схема.
Този процес е много по-малко болезнен при хранилища на данни без схеми. Всъщност в повечето случаи, когато просто добавяте и премахвате полета, то изобщо не съществува. Тъй като вашето хранилище за данни не разбира присъщите детайли на вашата схема, това означава, че вече не е проблем на ниво инфраструктура и може лесно да се обработва от логиката на приложението, ако е необходимо.
Необслужваемостта, липсата на схеми и ненатрапчивостта са основни дизайнерски качества, заложени в Redis и неговите операции. Например заявката за списък с последните публикации в BlogPost връща същия резултат за празен списък както би било в празна база данни Redis - 0 резултата. Тъй като стойностите в Redis са двоично безопасни низове, вие можете да съхранявате всичко, което пожелаете в тях и най-важното чрез разширение това означава, че всички операции на Redis могат да поддържат всички ваши типове приложения, без да се нуждаете от „междинен език“ като DDL за предоставяне на строга схема на това какво да очаквате. Без каквато и да е предварителна инициализация вашият код може да говори директно с Redis хранилище за данни естествено, сякаш е колекция в паметта.
За да илюстрирам какво може да се постигне на практика, ще премина през две различни стратегии за обработка на промените в схемата.
- Подходът без правене – при който добавянето, премахването на полета и неразрушителната промяна на типовете полета се обработват автоматично.
- Използване на персонализиран превод – използване на логика на ниво приложение за персонализиране на превода между стария и новия тип.
Пълният изходен код за този пример е достъпен тук.
Примерен код №
За да демонстрирам типичен сценарий за миграция, използвам BlogPost
тип, дефиниран на предишната страница, за да го проектирате в фундаментално различен New.BlogPost
Тип. Пълната дефиниция на стария и новия тип е показана по-долу:
Старата схема #
public class BlogPost
{
public BlogPost()
{
this.Categories = new List<string>();
this.Tags = new List<string>();
this.Comments = new List<BlogPostComment>();
}
public int Id { get; set; }
public int BlogId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<string> Categories { get; set; }
public List<string> Tags { get; set; }
public List<BlogPostComment> Comments { get; set; }
}
public class BlogPostComment
{
public string Content { get; set; }
public DateTime CreatedDate { get; set; }
}
Новата схема №
„Новата версия“ съдържа повечето промени, които е вероятно да срещнете при нормално разработване на приложения:
- Добавени, премахнати и преименувани полета
- Неразрушаваща промяна на
int
вlong
иdouble
полета - Променен тип колекция от маркери от
List
къмHashSet
- Промени строго въведен
BlogPostComment
въведете в свободно въведен низDictionary
- Въведе ново
enum
тип - Добавено е изчислено поле с нула
Нови типове схеми #
public class BlogPost
{
public BlogPost()
{
this.Labels = new List<string>();
this.Tags = new HashSet<string>();
this.Comments = new List<Dictionary<string, string>>();
}
//Changed int types to both a long and a double type
public long Id { get; set; }
public double BlogId { get; set; }
//Added new field
public BlogPostType PostType { get; set; }
public string Title { get; set; }
public string Content { get; set; }
//Renamed from 'Categories' to 'Labels'
public List<string> Labels { get; set; }
//Changed from List to a HashSet
public HashSet<string> Tags { get; set; }
//Changed from List of strongly-typed 'BlogPostComment' to loosely-typed string map
public List<Dictionary<string, string>> Comments { get; set; }
//Added pointless calculated field
public int? NoOfComments { get; set; }
}
public enum BlogPostType
{
None,
Article,
Summary,
}
1. Подходът без правене - използване на старите данни с новите типове #
Въпреки че е трудно за вярване, без допълнителни усилия можете просто да се преструвате, че не е направена промяна и свободен достъп до нови типове, разглеждащи стари данни. Това е възможно, когато има неразрушителни промени (т.е. няма загуба на информация) с нови типове полета. Примерът по-долу използва хранилището от предишния пример, за да попълни Redis с тестови данни от старите типове. Все едно нищо не се е случило, можете да прочетете старите данни, като използвате новия тип:
var repository = new BlogRepository(redisClient);
//Populate the datastore with the old schema from the 'BlogPostBestPractice'
BlogPostBestPractice.InsertTestData(repository);
//Create a typed-client based on the new schema
using (var redisBlogPosts = redisClient.GetTypedClient<New.BlogPost>())
{
//Automatically retrieve blog posts
IList<New.BlogPost> allBlogPosts = redisBlogPosts.GetAll();
//Print out the data in the list of 'New.BlogPost' populated from old 'BlogPost' type
Console.WriteLine(allBlogPosts.Dump());
/*Output:
[
{
Id: 3,
BlogId: 2,
PostType: None,
Title: Redis,
Labels: [],
Tags:
[
Redis,
NoSQL,
Scalability,
Performance
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 2010-04-28T21:42:03.9484725Z
}
]
},
{
Id: 4,
BlogId: 2,
PostType: None,
Title: Couch Db,
Labels: [],
Tags:
[
CouchDb,
NoSQL,
JSON
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 2010-04-28T21:42:03.9484725Z
}
]
},
{
Id: 1,
BlogId: 1,
PostType: None,
Title: RavenDB,
Labels: [],
Tags:
[
Raven,
NoSQL,
JSON,
.NET
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 2010-04-28T21:42:03.9004697Z
},
{
Content: Second Comment!,
CreatedDate: 2010-04-28T21:42:03.9004697Z
}
]
},
{
Id: 2,
BlogId: 1,
PostType: None,
Title: Cassandra,
Labels: [],
Tags:
[
Cassandra,
NoSQL,
Scalability,
Hashing
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 2010-04-28T21:42:03.9004697Z
}
]
}
]
*/
}
2. Използване на персонализиран превод за мигриране на данни с помощта на логиката на приложението #
Някои недостатъци на горния подход за „неправи нищо“ са, че ще загубите данните на „преименуваните полета“. Ще има и моменти, когато искате новомигрираните данни да имат специфични стойности, които са различни от вградените в .NET настройки по подразбиране. Когато искате повече контрол върху миграцията на старите си данни, добавянето на персонализиран превод е тривиално упражнение, когато можете да го направите естествено в код:
var repository = new BlogRepository(redisClient);
//Populate the datastore with the old schema from the 'BlogPostBestPractice'
BlogPostBestPractice.InsertTestData(repository);
//Create a typed-client based on the new schema
using (var redisBlogPosts = redisClient.GetTypedClient<BlogPost>())
using (var redisNewBlogPosts = redisClient.GetTypedClient<New.BlogPost>())
{
//Automatically retrieve blog posts
IList<BlogPost> oldBlogPosts = redisBlogPosts.GetAll();
//Write a custom translation layer to migrate to the new schema
var migratedBlogPosts = oldBlogPosts.ConvertAll(old => new New.BlogPost
{
Id = old.Id,
BlogId = old.BlogId,
Title = old.Title,
Content = old.Content,
Labels = old.Categories, //populate with data from renamed field
PostType = New.BlogPostType.Article, //select non-default enum value
Tags = old.Tags,
Comments = old.Comments.ConvertAll(x => new Dictionary<string, string>
{ { "Content", x.Content }, { "CreatedDate", x.CreatedDate.ToString() }, }),
NoOfComments = old.Comments.Count, //populate using logic from old data
});
//Persist the new migrated blogposts
redisNewBlogPosts.StoreAll(migratedBlogPosts);
//Read out the newly stored blogposts
var refreshedNewBlogPosts = redisNewBlogPosts.GetAll();
//Note: data renamed fields are successfully migrated to the new schema
Console.WriteLine(refreshedNewBlogPosts.Dump());
/*
[
{
Id: 3,
BlogId: 2,
PostType: Article,
Title: Redis,
Labels:
[
NoSQL,
Cache
],
Tags:
[
Redis,
NoSQL,
Scalability,
Performance
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 28/04/2010 22:58:35
}
],
NoOfComments: 1
},
{
Id: 4,
BlogId: 2,
PostType: Article,
Title: Couch Db,
Labels:
[
NoSQL,
DocumentDB
],
Tags:
[
CouchDb,
NoSQL,
JSON
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 28/04/2010 22:58:35
}
],
NoOfComments: 1
},
{
Id: 1,
BlogId: 1,
PostType: Article,
Title: RavenDB,
Labels:
[
NoSQL,
DocumentDB
],
Tags:
[
Raven,
NoSQL,
JSON,
.NET
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 28/04/2010 22:58:35
},
{
Content: Second Comment!,
CreatedDate: 28/04/2010 22:58:35
}
],
NoOfComments: 2
},
{
Id: 2,
BlogId: 1,
PostType: Article,
Title: Cassandra,
Labels:
[
NoSQL,
Cluster
],
Tags:
[
Cassandra,
NoSQL,
Scalability,
Hashing
],
Comments:
[
{
Content: First Comment!,
CreatedDate: 28/04/2010 22:58:35
}
],
NoOfComments: 1
}
]
*/
}
Крайният резултат е хранилище за данни, изпълнено с нови данни, попълнени точно по начина, по който искате - готов да обслужва функциите на вашето ново приложение. За разлика от това, опитът за горното в типично решение за RDBMS без прекъсване е ефективно подвиг на магия, който се възнаграждава с 999 точки за препълване на стека и лично съболезнование от неговия велик канцлер @JonSkeet 😃
Надявам се, че това ясно илюстрира разликите между двете технологии. На практика ще бъдете изумени от увеличенията на производителността, които стават възможни, когато не е нужно да моделирате приложението си, за да пасне около ORM и RDBMS и можете да спестявате обекти като памет.
Винаги е добра идея да се изложите на новите технологии, така че ако все още не сте го направили, каня ви да започнете да разработвате с Redis днес, за да видите ползите сами. За да започнете, всичко, от което се нуждаете, е екземпляр на redis-сървъра (не се изисква конфигурация, просто разархивирайте и стартирайте) и C# Redis Client на ServiceStack без зависимости и сте готови!