Cette page présente un exemple typique pour montrer à quel point les migrations de données typiques peuvent être indolores lors de l'utilisation de Redis et d'autres magasins de données sans schéma NoSQL.
Toutes les pages d'application du blog Redis #
- Concevoir une base de données NoSQL avec Redis
- Migrations de données simples à l'aide de Redis et d'autres datastores NoSQL sans schéma
Migrations de données indolores avec des datastores NoSQL sans schéma et Redis #
Développer nouveau Les systèmes de base de données greenfield utilisant un back-end RDBMS sont généralement une expérience sans problème. Avant que le système ne soit opérationnel, vous pouvez facilement modifier un schéma en analysant l'ensemble de la base de données d'application et en le recréant avec des scripts DDL automatisés qui le créeront et le rempliront avec des données de test adaptées à votre nouveau schéma.
Les vrais problèmes de votre vie informatique surviennent après votre premier déploiement et la mise en service de votre système. À ce stade, vous n'avez plus la possibilité de nuke la base de données et de la recréer à partir de zéro. Si vous avez de la chance, vous avez un script en place qui peut déduire automatiquement les instructions DDL requises pour migrer de votre ancien schéma vers le nouveau. Cependant, toute modification importante de votre schéma est susceptible d'impliquer des nuits tardives, des temps d'arrêt et un effort non négligeable pour assurer une migration réussie vers le nouveau schéma de base de données.
Ce processus est beaucoup moins pénible avec les magasins de données sans schéma. En fait, dans la plupart des cas, lorsque vous ajoutez et supprimez des champs, il n'existe pas du tout. Comme votre magasin de données ne comprend pas les détails intrinsèques de votre schéma, cela signifie qu'il ne s'agit plus d'un problème au niveau de l'infrastructure et qu'il peut facilement être géré par la logique d'application si nécessaire.
Être sans maintenance, sans schéma et non intrusif sont des qualités de conception fondamentales intégrées à Redis et à ses opérations. Par exemple, interroger une liste de BlogPosts récents renvoie le même résultat pour une liste vide comme dans une base de données Redis vide - 0 résultats. Comme les valeurs dans Redis sont des chaînes binaires sûres, vous pouvez y stocker tout ce que vous voulez et, plus important encore, par extension, cela signifie que toutes les opérations Redis peuvent prendre en charge tous vos types d'application sans avoir besoin d'un "langage intermédiaire" comme DDL pour fournir un schéma rigide de ce à quoi s'attendre. Sans aucune initialisation préalable, votre code peut communiquer directement avec un magasin de données Redis, naturellement, comme s'il s'agissait d'une collection en mémoire.
Pour illustrer ce qui peut être réalisé dans la pratique, je vais passer en revue deux stratégies différentes de gestion des changements de schéma.
- L'approche "ne rien faire" :l'ajout, la suppression de champs et la modification non destructive des types de champs sont automatiquement gérés.
- Utilisation d'une traduction personnalisée :utilisation de la logique au niveau de l'application pour personnaliser la traduction entre les anciens et les nouveaux types.
Le code source exécutable complet de cet exemple est disponible ici.
Exemple de code #
Pour illustrer un scénario de migration typique, j'utilise le BlogPost
type défini sur la page précédente pour le projeter vers un New.BlogPost
fondamentalement différent taper. La définition complète des anciens et des nouveaux types est présentée ci-dessous :
L'ancien 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; }
}
Le nouveau schéma #
La "nouvelle version" contient la plupart des modifications que vous êtes susceptible de rencontrer lors du développement normal d'une application :
- Champs ajoutés, supprimés et renommés
- Modification non destructive de
int
enlong
etdouble
champs - Modification du type de collection de balises à partir d'une
List
à unHashSet
- Modification d'un
BlogPostComment
fortement typé tapez dans une chaîne vaguement typéeDictionary
- Introduction d'un nouveau
enum
taper - Ajout d'un champ calculé nullable
Nouveaux types de schéma #
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. L'approche "ne rien faire" - utiliser les anciennes données avec les nouveaux types #
Bien que difficile à croire, sans effort supplémentaire, vous pouvez simplement prétendre qu'aucun changement n'a été fait et accéder librement à de nouveaux types en consultant d'anciennes données. Cela est possible lorsqu'il y a des changements non destructifs (c'est-à-dire sans perte d'informations) avec de nouveaux types de champs. L'exemple ci-dessous utilise le référentiel de l'exemple précédent pour remplir Redis avec les données de test des anciens types. Comme si rien ne s'était passé, vous pouvez lire les anciennes données en utilisant le nouveau 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. Utiliser une traduction personnalisée pour migrer des données à l'aide de la logique d'application #
Certains inconvénients de l'approche "ne rien faire" ci-dessus sont que vous perdrez les données des "champs renommés". Il y aura également des moments où vous souhaiterez que les données nouvellement migrées aient des valeurs spécifiques différentes des valeurs par défaut intégrées de .NET. Lorsque vous souhaitez avoir plus de contrôle sur la migration de vos anciennes données, ajouter une traduction personnalisée est un exercice trivial quand vous pouvez le faire nativement dans le code :
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
}
]
*/
}
Le résultat final est un magasin de données rempli de nouvelles données remplies exactement comme vous le souhaitez - prêt à servir les fonctionnalités de votre nouvelle application. En revanche, tenter ce qui précède dans une solution RDBMS typique sans aucun temps d'arrêt est en fait un tour de magie qui est récompensé par 999 points Stack Overflow et une condoléance personnelle de son grand chancelier @JonSkeet 😃
J'espère que cela illustre clairement les différences entre les deux technologies. En pratique, vous serez surpris par les gains de productivité rendus possibles lorsque vous n'avez pas à modéliser votre application pour qu'elle s'adapte à un ORM et à un SGBDR et que vous puissiez enregistrer des objets comme s'il s'agissait de mémoire.
C'est toujours une bonne idée de s'exposer aux nouvelles technologies, donc si vous ne l'avez pas déjà fait, je vous invite à commencer à développer avec Redis dès aujourd'hui pour voir les avantages par vous-même. Pour commencer, tout ce dont vous avez besoin est une instance du serveur redis (aucune configuration requise, il suffit de décompresser et d'exécuter) et le client C# Redis de ServiceStack sans dépendance et vous êtes prêt à partir !