Den här sidan går igenom ett typiskt exempel för att visa hur smärtfria typiska datamigreringar kan vara när du använder Redis och andra NoSQL-schemalösa datalager.
Alla Redis Blog-ansökningssidor #
- Designa en NoSQL-databas med Redis
- Smärtfria datamigreringar med Redis och andra schemalösa NoSQL-datalager
Smärtfria datamigrationer med schemalösa NoSQL-datalager och Redis #
Utvecklar nytt greenfield-databassystem som använder en RDBMS-backend är för det mesta en problemfri upplevelse. Innan systemet är live kan du enkelt ändra ett schema genom att nucka hela applikationsdatabasen och återskapa det med automatiska DDL-skript som kommer att skapa och fylla i det med testdata som passar ditt nya schema.
De verkliga problemen i ditt IT-liv inträffar efter din första driftsättning och ditt system går live. Vid den tidpunkten har du inte längre möjlighet att kärnvapen i databasen och återskapa den från grunden. Om du har tur har du ett skript på plats som automatiskt kan sluta sig till de nödvändiga DDL-satserna för att migrera från ditt gamla schema till ditt nya. Men alla betydande ändringar av ditt schema kommer sannolikt att involvera sena nätter, driftstopp och en icke-trivial ansträngning för att säkerställa en framgångsrik migrering till det nya db-schemat.
Denna process är mycket mindre smärtsam med schemalösa datalager. Faktum är att i de flesta fall när du bara lägger till och tar bort fält finns det inte alls. Genom att inte låta din databutik förstå inneboende detaljer i ditt schema betyder det att det inte längre är ett problem på infrastrukturnivå och att det enkelt kan hanteras av applikationslogik om det behövs.
Att vara underhållsfri, schemafri och icke-påträngande är grundläggande designegenskaper inbakade i Redis och dess verksamhet. Att till exempel fråga efter en lista med senaste blogginlägg returnerar samma resultat för en tom lista som det skulle göra i en tom Redis-databas - 0 resultat. Eftersom värden i Redis är binärt säkra strängar kan du lagra vad du vill i dem och viktigast av allt betyder detta att alla Redis-operationer kan stödja alla dina applikationstyper utan att behöva ett "mellanspråk" som DDL för att tillhandahålla en stela schema för vad som väntar. Utan föregående initiering kan din kod tala direkt till en Redis-databutik naturligt som om det vore en samling i minnet.
För att illustrera vad som kan uppnås i praktiken kommer jag att gå igenom två olika strategier för att hantera schemaändringar.
- Gör ingenting - där tillägg, borttagning av fält och oförstörande ändring av fälttyper hanteras automatiskt.
- Använda en anpassad översättning – med logik på programnivå för att anpassa översättningen mellan den gamla och den nya typen.
Den fullständiga körbara källkoden för detta exempel finns tillgänglig här.
Exempelkod #
För att visa ett typiskt migreringsscenario använder jag BlogPost
typ definierad på föregående sida för att projicera den till en fundamentalt annorlunda New.BlogPost
typ. Den fullständiga definitionen av de gamla och nya typerna visas nedan:
Det gamla schemat #
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; }
}
Det nya schemat #
Den "nya versionen" innehåller de flesta ändringar som du sannolikt kommer att stöta på i normal apputveckling:
- Tillagda, borttagna och bytt namn på fält
- Icke-förstörande ändring av
int
tilllong
ochdouble
fält - Ändrad taggsamlingstyp från en
List
till enHashSet
- Ändrade en starkt skriven
BlogPostComment
skriv i en löst skriven strängDictionary
- Introducerade en ny
enum
typ - Har lagt till ett nullbart beräknat fält
Nya schematyper #
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. Gör-ingenting-metoden - använd den gamla datan med de nya typerna #
Även om det är svårt att tro, utan extra ansträngning kan du bara låtsas att ingen förändring faktiskt gjordes och fritt få tillgång till nya typer som tittar på gamla data. Detta är möjligt när det finns oförstörande förändringar (dvs ingen förlust av information) med nya fälttyper. Exemplet nedan använder arkivet från föregående exempel för att fylla Redis med testdata från de gamla typerna. Precis som om ingenting hände kan du läsa den gamla informationen med den nya typen:
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. Använda en anpassad översättning för att migrera data med applikationslogik #
Några nackdelar med ovanstående "gör-ingenting"-metoden är att du kommer att förlora data från "döpta fält". Det kommer också att finnas tillfällen då du vill att den nyligen migrerade datan ska ha specifika värden som skiljer sig från de inbyggda .NET-standardinställningarna. När du vill ha mer kontroll över migreringen av din gamla data är det en trivial övning att lägga till en anpassad översättning när du kan göra det inbyggt i kod:
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
}
]
*/
}
Slutresultatet är en databutik fylld med ny data fylld på precis som du vill ha den - redo att servera funktioner i din nya applikation. Däremot är att försöka ovanstående i en typisk RDBMS-lösning utan driftstopp i praktiken en magisk bedrift som belönas med 999 Stack Overflow-poäng och ett personligt kondoleans från dess storkansler @JonSkeet 😃
Jag hoppas att detta tydligt illustrerar skillnaderna mellan de två teknikerna. I praktiken kommer du att bli förvånad över produktivitetsvinsterna som görs möjliga när du inte behöver modellera din applikation för att passa runt en ORM och en RDBMS och kan spara objekt som om det var minne.
Det är alltid en bra idé att exponera dig själv för ny teknik så om du inte redan har gjort det, inbjuder jag dig att börja utveckla med Redis idag för att se fördelarna själv. Allt du behöver för att komma igång är en instans av redis-servern (ingen konfiguration krävs, bara packa upp och kör) och den beroendefria ServiceStacks C# Redis-klient och du är redo att gå!