Deze pagina doorloopt een typisch voorbeeld om te laten zien hoe pijnloos typische gegevensmigraties kunnen zijn bij gebruik van Redis en andere NoSQL-schemaloze gegevensarchieven.
Alle toepassingspagina's van Redis Blog #
- Een NoSQL-database ontwerpen met Redis
- Pijnloze datamigraties met Redis en andere schemaloze NoSQL-datastores
Pijnloze datamigraties met schemaloze NoSQL-datastores en Redis #
Ontwikkelen van nieuwe greenfield-databasesystemen die een RDBMS-back-end gebruiken, zijn meestal een probleemloze ervaring. Voordat het systeem live is, kunt u eenvoudig een schema wijzigen door de volledige toepassingsdatabase te 'nuken' en deze opnieuw te maken met geautomatiseerde DDL-scripts die deze maken en vullen met testgegevens die passen bij uw nieuwe schema.
De echte problemen in uw IT-leven gebeuren na uw eerste implementatie en uw systeem gaat live. Op dat moment heb je niet langer de mogelijkheid om de database te vernietigen en helemaal opnieuw te maken. Als je geluk hebt, heb je een script dat automatisch de vereiste DDL-instructies kan afleiden om van je oude schema naar je nieuwe te migreren. Maar alle belangrijke wijzigingen in uw schema zullen waarschijnlijk late nachten, uitvaltijd en een niet-triviale hoeveelheid inspanning met zich meebrengen om een succesvolle migratie naar het nieuwe db-schema te garanderen.
Dit proces is veel minder pijnlijk met gegevensopslag zonder schema. In de meeste gevallen, wanneer u alleen velden toevoegt en verwijdert, bestaat het zelfs helemaal niet. Doordat uw datastore de intrinsieke details van uw schema niet begrijpt, betekent dit dat het niet langer een probleem op infrastructuurniveau is en indien nodig eenvoudig kan worden afgehandeld door toepassingslogica.
Onderhoudsvrij, schemaloos en niet-intrusief zijn fundamentele ontwerpkwaliteiten die in Redis en zijn activiteiten zijn ingebakken. Als u bijvoorbeeld een lijst met recente BlogPosts opvraagt, krijgt u hetzelfde resultaat voor een lege lijst zoals het zou zijn in een lege Redis-database - 0 resultaten. Aangezien waarden in Redis binair veilige tekenreeksen zijn, kunt u er alles in opslaan wat u maar wilt en vooral bij uitbreiding betekent dit dat alle Redis-bewerkingen al uw toepassingstypen kunnen ondersteunen zonder dat een 'tussenliggende taal' zoals DDL nodig is om een rigide schema van wat te verwachten. Zonder enige voorafgaande initialisatie kan uw code op natuurlijke wijze rechtstreeks met een Redis-datastore communiceren alsof het een verzameling in het geheugen is.
Om te illustreren wat er in de praktijk kan worden bereikt, zal ik twee verschillende strategieën voor het omgaan met schemawijzigingen doornemen.
- De niets-doen-benadering - waarbij het toevoegen, verwijderen van velden en niet-destructieve wijziging van veldtypen automatisch wordt afgehandeld.
- Een aangepaste vertaling gebruiken - logica op applicatieniveau gebruiken om de vertaling tussen de oude en nieuwe typen aan te passen.
De volledige uitvoerbare broncode voor dit voorbeeld is hier beschikbaar.
Voorbeeldcode #
Om een typisch migratiescenario te demonstreren, gebruik ik de BlogPost
type gedefinieerd op de vorige pagina om het te projecteren op een fundamenteel andere New.BlogPost
type. De volledige definitie van de oude en nieuwe typen wordt hieronder weergegeven:
Het oude schema #
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; }
}
Het nieuwe schema #
De 'nieuwe versie' bevat de meeste wijzigingen die u waarschijnlijk zult tegenkomen bij normale app-ontwikkeling:
- Velden toegevoegd, verwijderd en hernoemd
- Niet-destructieve wijziging van
int
inlong
endouble
velden - Gewijzigd tagverzamelingstype van een
List
naar eenHashSet
- Een sterk getypte
BlogPostComment
typ in een losjes getypte stringDictionary
- Introductie van een nieuwe
enum
typ - Een nullable berekend veld toegevoegd
Nieuwe schematypes #
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. De niets-doen-benadering - de oude gegevens gebruiken met de nieuwe typen #
Hoewel het moeilijk te geloven is, kun je zonder extra inspanning gewoon doen alsof er niets is veranderd en vrij toegang tot nieuwe typen die oude gegevens bekijken. Dit is mogelijk wanneer er niet-destructieve wijzigingen zijn (d.w.z. geen verlies van informatie) met nieuwe veldtypen. In het onderstaande voorbeeld wordt de repository uit het vorige voorbeeld gebruikt om Redis te vullen met testgegevens van de oude typen. Net alsof er niets is gebeurd, kunt u de oude gegevens lezen met het nieuwe type:
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. Een aangepaste vertaling gebruiken om gegevens te migreren met toepassingslogica #
Een nadeel van bovenstaande 'niets doen'-aanpak is dat je de gegevens van 'hernoemde velden' kwijtraakt. Er zullen ook momenten zijn waarop u wilt dat de nieuw gemigreerde gegevens specifieke waarden hebben die verschillen van de ingebouwde .NET-standaarden. Als u meer controle wilt over de migratie van uw oude gegevens, is het toevoegen van een aangepaste vertaling een triviale oefening als u dit native in code kunt doen:
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
}
]
*/
}
Het eindresultaat is een datastore gevuld met nieuwe data die precies zo wordt ingevuld als u dat wilt - klaar om functies van uw nieuwe applicatie te leveren. Daarentegen is het proberen van het bovenstaande in een typische RDBMS-oplossing zonder enige downtime in feite een magisch staaltje dat wordt beloond met 999 Stack Overflow-punten en een persoonlijk condoleance van zijn grootkanselier @JonSkeet 😃
Ik hoop dat dit duidelijk de verschillen tussen de twee technologieën illustreert. In de praktijk zult u versteld staan van de productiviteitswinst die mogelijk wordt gemaakt wanneer u uw toepassing niet hoeft te modelleren om rond een ORM en een RDBMS te passen en objecten kunt opslaan alsof het geheugen is.
Het is altijd een goed idee om jezelf bloot te stellen aan nieuwe technologieën, dus als je dat nog niet hebt gedaan, nodig ik je uit om vandaag met Redis aan de slag te gaan om de voordelen voor jezelf te zien. Om te beginnen heb je alleen een instantie van de redis-server nodig (geen configuratie vereist, gewoon uitpakken en uitvoeren) en de C# Redis Client van de afhankelijkheidsvrije ServiceStack C# Redis Client en je bent klaar om aan de slag te gaan!