Tato stránka obsahuje typický příklad, který ukazuje, jak bezbolestná může být typická migrace dat při použití Redis a dalších datových úložišť NoSQL bez schématu.
Všechny stránky aplikace Redis Blog #
- Návrh databáze NoSQL pomocí Redis
- Bezbolestná migrace dat pomocí Redis a dalších datových úložišť NoSQL bez schémat
Bezbolestná migrace dat s datovými úložišti NoSQL bez schémat a Redis #
Vývoj nového databázové systémy na zelené louce využívající back-end RDBMS jsou většinou bezproblémové. Než bude systém spuštěn, můžete schéma snadno upravit tak, že nuknete celou databázi aplikace a znovu ji vytvoříte pomocí automatických skriptů DDL, které jej vytvoří a naplní testovacími daty, která odpovídají vašemu novému schématu.
Skutečné problémy ve vašem životě IT nastanou po prvním nasazení a spuštění systému. V tomto okamžiku již nemáte možnost databázi nukleovat a znovu ji vytvořit od nuly. Pokud budete mít štěstí, máte skript, který dokáže automaticky odvodit požadované příkazy DDL pro migraci z vašeho starého schématu do nového. Jakékoli významné změny vašeho schématu však budou pravděpodobně zahrnovat pozdní noci, prostoje a netriviální úsilí k zajištění úspěšné migrace na nové schéma db.
Tento proces je mnohem méně bolestivý s datovými úložišti bez schématu. Ve skutečnosti ve většině případů, když pouze přidáváte a odebíráte pole, vůbec neexistuje. Tím, že vaše datové úložiště nerozumí vnitřním detailům vašeho schématu, to znamená, že se již nejedná o problém na úrovni infrastruktury a v případě potřeby jej lze snadno vyřešit aplikační logikou.
Bezúdržbovost, schémata a nevtíravost jsou základními designovými vlastnostmi, které jsou součástí Redis a jeho provozů. Například dotaz na seznam posledních blogových příspěvků vrátí stejný výsledek pro prázdný seznam jako by tomu bylo v prázdné databázi Redis - 0 výsledků. Protože hodnoty v Redis jsou binárně bezpečné řetězce, můžete do nich ukládat cokoli, co chcete, a co je nejdůležitější, rozšířením to znamená, že všechny operace Redis mohou podporovat všechny vaše typy aplikací, aniž byste potřebovali „středně pokročilý jazyk“, jako je DDL k poskytování pevné schéma toho, co očekávat. Bez jakékoli předchozí inicializace může váš kód komunikovat přímo s datovým úložištěm Redis přirozeně, jako by to byla kolekce v paměti.
Abych ilustroval, čeho lze v praxi dosáhnout, provedu dvě různé strategie zpracování změn schématu.
- Přístup nicnedělání – kde se přidávání, odebírání polí a nedestruktivní změna typů polí řeší automaticky.
- Použití vlastního překladu – použití logiky na úrovni aplikace k přizpůsobení překladu mezi starými a novými typy.
Úplný spustitelný zdrojový kód pro tento příklad je k dispozici zde.
Příklad kódu #
K demonstraci typického scénáře migrace používám BlogPost
zadejte na předchozí stránce, abyste jej promítli do zásadně odlišného New.BlogPost
typ. Úplná definice starého a nového typu je uvedena níže:
Staré schéma #
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; }
}
Nové schéma #
„Nová verze“ obsahuje většinu změn, se kterými se pravděpodobně setkáte při běžném vývoji aplikací:
- Přidaná, odstraněná a přejmenovaná pole
- Nedestruktivní změna
int
dolong
adouble
pole - Změnil se typ kolekce značek ze
List
doHashSet
- Změnil silně napsaný
BlogPostComment
zadejte do volně napsaného řetězceDictionary
- Zaveden nový
enum
typ - Přidáno vypočítané pole s možnou hodnotou Null
Nové typy schémat #
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. Přístup nicnedělání – použití starých dat s novými typy #
I když je to těžké uvěřit, bez dalšího úsilí můžete jen předstírat, že žádná změna ve skutečnosti nebyla provedena a volný přístup k novým typům prohlížení starých dat. To je možné, když dochází k nedestruktivním změnám (tj. bez ztráty informací) s novými typy polí. Níže uvedený příklad používá úložiště z předchozího příkladu k naplnění Redis testovacími daty ze starých typů. Stejně jako kdyby se nic nestalo, můžete číst stará data pomocí nového typu:
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. Použití vlastního překladu k migraci dat pomocí aplikační logiky #
Některými nevýhodami výše uvedeného přístupu „nedělat nic“ je, že ztratíte data „přejmenovaných polí“. Nastanou také situace, kdy budete chtít, aby nově migrovaná data měla specifické hodnoty, které se liší od výchozích vestavěných .NET. Pokud chcete mít větší kontrolu nad migrací svých starých dat, přidání vlastního překladu je triviální cvičení, když to můžete udělat nativně v kódu:
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
}
]
*/
}
Konečným výsledkem je datové úložiště naplněné novými daty naplněnými přesně tak, jak chcete – připravené sloužit funkcím vaší nové aplikace. Naproti tomu pokus o výše uvedené v typickém řešení RDBMS bez jakýchkoli prostojů je v podstatě magickým činem, který je odměněn 999 body Stack Overflow a osobní soustrast od jeho velkého kancléře @JonSkeet 😃
Doufám, že to jasně ilustruje rozdíly mezi těmito dvěma technologiemi. V praxi budete ohromeni možnými nárůsty produktivity, když nemusíte svou aplikaci modelovat tak, aby vyhovovala ORM a RDBMS a můžete ukládat objekty, jako by to byla paměť.
Vždy je dobré vystavit se novým technologiím, takže pokud jste tak ještě neučinili, zvu vás, abyste dnes začali s vývojem s Redis, abyste sami viděli výhody. Abyste mohli začít, vše, co potřebujete, je instance redis-serveru (není nutná žádná konfigurace, stačí rozbalit a spustit) a bezzávislého klienta ServiceStack C# Redis a jste připraveni jít!