Redis
 sql >> Datenbank >  >> NoSQL >> Redis

Datenmigrationen mit Redis

Diese Seite führt Sie durch ein typisches Beispiel, um zu zeigen, wie schmerzlos typische Datenmigrationen sein können, wenn Sie Redis und andere schemalose NoSQL-Datenspeicher verwenden.

Alle Redis-Blog-Anwendungsseiten #

  • Entwerfen einer NoSQL-Datenbank mit Redis
  • Schmerzlose Datenmigrationen mit Redis und anderen schemalosen NoSQL-Datenspeichern

Schmerzlose Datenmigrationen mit schemalosen NoSQL-Datenspeichern und Redis #

neu entwickeln Greenfield-Datenbanksysteme, die ein RDBMS-Backend verwenden, sind meistens eine problemlose Erfahrung. Bevor das System live ist, können Sie ein Schema einfach ändern, indem Sie die gesamte Anwendungsdatenbank löschen und mit automatisierten DDL-Skripts neu erstellen, die es erstellen und mit Testdaten füllen, die zu Ihrem neuen Schema passen.

Die wirklichen Probleme in Ihrem IT-Leben treten auf, nachdem Sie das erste Mal bereitgestellt und Ihr System live geschaltet haben. An diesem Punkt haben Sie nicht mehr die Möglichkeit, die Datenbank zu zerstören und von Grund auf neu zu erstellen. Wenn Sie Glück haben, verfügen Sie über ein Skript, das die erforderlichen DDL-Anweisungen automatisch ableiten kann, um von Ihrem alten Schema zu Ihrem neuen zu migrieren. Jedoch sind alle signifikanten Änderungen an Ihrem Schema wahrscheinlich mit langen Nächten, Ausfallzeiten und einem nicht unerheblichen Aufwand verbunden, um eine erfolgreiche Migration auf das neue Datenbankschema sicherzustellen.

Dieser Prozess ist mit schemalosen Datenspeichern viel weniger schmerzhaft. Tatsächlich ist es in den meisten Fällen, wenn Sie nur Felder hinzufügen und entfernen, überhaupt nicht vorhanden. Da Ihr Datenspeicher die intrinsischen Details Ihres Schemas nicht versteht, bedeutet dies, dass es sich nicht mehr um ein Problem auf Infrastrukturebene handelt und bei Bedarf einfach von der Anwendungslogik gehandhabt werden kann.

Wartungsfrei, schemalos und nicht aufdringlich zu sein, sind grundlegende Designqualitäten, die in Redis und seinen Betrieb einfließen. Beispielsweise liefert die Abfrage einer Liste der letzten BlogPosts das gleiche Ergebnis für eine leere Liste wie in einer leeren Redis-Datenbank - 0 Ergebnisse. Da Werte in Redis binärsichere Zeichenfolgen sind, können Sie alles speichern, was Sie wollen, und vor allem bedeutet dies, dass alle Redis-Operationen alle Ihre Anwendungstypen unterstützen können, ohne dass eine „Zwischensprache“ wie DDL erforderlich ist, um eine bereitzustellen starres Schema dessen, was zu erwarten ist. Ohne vorherige Initialisierung kann Ihr Code natürlich direkt mit einem Redis-Datenspeicher kommunizieren, als wäre es eine In-Memory-Sammlung.

Um zu veranschaulichen, was in der Praxis erreicht werden kann, werde ich zwei verschiedene Strategien zum Umgang mit Schemaänderungen durchgehen.

  • Der Do-Nothing-Ansatz – bei dem das Hinzufügen und Entfernen von Feldern und die zerstörungsfreie Änderung von Feldtypen automatisch gehandhabt werden.
  • Verwenden einer benutzerdefinierten Übersetzung – Verwenden von Logik auf Anwendungsebene, um die Übersetzung zwischen dem alten und dem neuen Typ anzupassen.

Der vollständige ausführbare Quellcode für dieses Beispiel ist hier verfügbar.

Beispielcode #

Um ein typisches Migrationsszenario zu demonstrieren, verwende ich den BlogPost Typ, der auf der vorherigen Seite definiert wurde, um ihn auf einen grundlegend anderen New.BlogPost zu projizieren Typ. Die vollständige Definition der alten und neuen Typen wird unten gezeigt:

Das alte 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; }
}

Das neue Schema #

Die „neue Version“ enthält die meisten Änderungen, denen Sie wahrscheinlich bei der normalen App-Entwicklung begegnen werden:

  • Hinzugefügte, entfernte und umbenannte Felder
  • Nicht-destruktive Änderung von int in long und double Felder
  • Tag-Sammlungstyp von einer List geändert zu einem HashSet
  • Ein stark typisiertes BlogPostComment wurde geändert Geben Sie in eine lose typisierte Zeichenfolge Dictionary ein
  • Neue enum eingeführt eingeben
  • Ein nullable berechnetes Feld hinzugefügt

Neue Schematypen #

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. Der Do-Nothing-Ansatz - die alten Daten mit den neuen Typen verwenden #

Obwohl es kaum zu glauben ist, können Sie ohne zusätzlichen Aufwand so tun, als ob tatsächlich keine Änderung vorgenommen wurde und greifen Sie frei auf neue Typen zu, indem Sie alte Daten betrachten. Dies ist möglich, wenn bei neuen Feldtypen zerstörungsfreie Änderungen (d. h. kein Informationsverlust) vorgenommen werden. Das folgende Beispiel verwendet das Repository aus dem vorherigen Beispiel, um Redis mit Testdaten der alten Typen zu füllen. Als ob nichts passiert wäre, können Sie die alten Daten mit dem neuen Typ lesen:

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. Verwenden einer benutzerdefinierten Übersetzung zum Migrieren von Daten mithilfe der Anwendungslogik #

Einige Nachteile des oben beschriebenen „Nichtstun“-Ansatzes bestehen darin, dass Sie die Daten von „umbenannten Feldern“ verlieren. Es wird auch Zeiten geben, in denen Sie möchten, dass die neu migrierten Daten bestimmte Werte haben, die sich von den integrierten Standardwerten von .NET unterscheiden. Wenn Sie mehr Kontrolle über die Migration Ihrer alten Daten haben möchten, ist das Hinzufügen einer benutzerdefinierten Übersetzung eine triviale Übung, wenn Sie dies nativ im Code tun können:

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
        }
    ]

     */
}

Das Endergebnis ist ein Datenspeicher, der mit neuen Daten gefüllt ist, die genau so gefüllt sind, wie Sie es möchten – bereit, Funktionen Ihrer neuen Anwendung bereitzustellen. Im Gegensatz dazu ist der Versuch des oben Gesagten in einer typischen RDBMS-Lösung ohne Ausfallzeiten praktisch eine magische Leistung, die mit 999 Stack Overflow-Punkten und einem persönlichen Beileid von seinem Großkanzler @JonSkeet belohnt wird 😃

Ich hoffe, dies verdeutlicht die Unterschiede zwischen den beiden Technologien. In der Praxis werden Sie von den möglichen Produktivitätsgewinnen erstaunt sein, wenn Sie Ihre Anwendung nicht so modellieren müssen, dass sie um ein ORM und ein RDBMS herum passt, und Objekte wie Arbeitsspeicher speichern können.

Es ist immer eine gute Idee, sich neuen Technologien auszusetzen. Wenn Sie dies noch nicht getan haben, lade ich Sie ein, noch heute mit der Entwicklung mit Redis zu beginnen, um die Vorteile selbst zu sehen. Um loszulegen, benötigen Sie lediglich eine Instanz des Redis-Servers (keine Konfiguration erforderlich, einfach entpacken und ausführen) und den C#-Redis-Client von ServiceStack ohne Abhängigkeiten, und Sie können loslegen!


No