sql >> Database teknologi >  >> NoSQL >> Redis

Datamigrering med Redis

Denne side gennemgår et typisk eksempel for at vise, hvor smertefri typiske datamigreringer kan være, når du bruger Redis og andre NoSQL-skemaløse datalagre.

Alle Redis Blog ansøgningssider #

  • Design af en NoSQL-database ved hjælp af Redis
  • Smertefri datamigrering ved hjælp af Redis og andre skemaløse NoSQL-datalagre

Smertefri datamigrering med skemaløse NoSQL-datalagre og Redis #

Udvikler nyt greenfield-databasesystemer, der bruger en RDBMS-backend, er for det meste en problemfri oplevelse. Før systemet er live, er du i stand til nemt at ændre et skema ved at nuke hele applikationsdatabasen og genskabe det med automatiserede DDL-scripts, der vil oprette og udfylde det med testdata, der passer til dit nye skema.

De virkelige problemer i dit it-liv opstår efter din første implementering, og dit system går live. På det tidspunkt har du ikke længere mulighed for at nuke databasen og genskabe den fra bunden. Hvis du er heldig, har du et script på plads, der automatisk kan udlede de nødvendige DDL-sætninger til at migrere fra dit gamle skema til dit nye. Men alle væsentlige ændringer af dit skema vil sandsynligvis involvere sene nætter, nedetid og en ikke-triviel indsats for at sikre en vellykket migrering til det nye db-skema.

Denne proces er meget mindre smertefuld med skemaløse datalagre. Faktisk eksisterer det i de fleste tilfælde slet ikke, når du blot tilføjer og fjerner felter. Ved ikke at have dit datalager til at forstå de iboende detaljer i dit skema, betyder det, at det ikke længere er et problem på infrastrukturniveau og nemt kan håndteres af applikationslogik, hvis det er nødvendigt.

At være vedligeholdelsesfri, skemafri og ikke-påtrængende er grundlæggende designkvaliteter, der er indbygget i Redis og dets drift. For eksempel returnerer en forespørgsel på en liste over seneste blogindlæg det samme resultat for en tom liste som i en tom Redis-database - 0 resultater. Da værdier i Redis er binært sikre strenge, er du i stand til at gemme alt, hvad du vil i dem, og vigtigst af alt betyder dette, at alle Redis-operationer kan understøtte alle dine applikationstyper uden at skulle bruge et 'mellemsprog' som DDL for at give en stift skema over, hvad man kan forvente. Uden nogen forudgående initialisering kan din kode tale direkte til et Redis-datalager naturligt, som om det var en samling i hukommelsen.

For at illustrere, hvad der kan opnås i praksis, vil jeg gennemgå to forskellige strategier til håndtering af skemaændringer.

  • Do-nothing-tilgangen - hvor tilføjelse, fjernelse af felter og ikke-destruktiv ændring af felttyper håndteres automatisk.
  • Brug af en tilpasset oversættelse - brug af logik på applikationsniveau til at tilpasse oversættelsen mellem de gamle og nye typer.

Den fulde kørebare kildekode til dette eksempel er tilgængelig her.

Eksempelkode #

For at demonstrere et typisk migreringsscenarie bruger jeg BlogPost type defineret på den forrige side for at projicere den til en fundamentalt anden New.BlogPost type. Den fulde definition af de gamle og nye typer er vist nedenfor:

Det gamle skema #

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 nye skema #

Den 'nye version' indeholder de fleste ændringer, du sandsynligvis vil støde på i normal appudvikling:

  • Tilføjede, fjernede og omdøbte felter
  • Ikke-destruktiv ændring af int til long og double felter
  • Ændret tagsamlingstype fra en List til et HashSet
  • Ændrede en stærkt indtastet BlogPostComment skriv i en løst skrevet streng Dictionary
  • Introducerede en ny enum type
  • Tilføjet et nullbart beregnet felt

Nye skematyper #

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-intet-tilgangen - brug af de gamle data med de nye typer #

Selvom det er svært at tro, kan du uden ekstra indsats bare lade som om, ingen ændring faktisk blev foretaget og fri adgang til nye typer, der ser på gamle data. Dette er muligt, når der er ikke-destruktive ændringer (dvs. intet tab af information) med nye felttyper. Eksemplet nedenfor bruger repository fra det forrige eksempel til at udfylde Redis med testdata fra de gamle typer. Ligesom om intet skete, kan du læse de gamle data ved hjælp af den nye 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. Brug af en tilpasset oversættelse til at migrere data ved hjælp af applikationslogik #

Nogle ulemper ved ovenstående 'gør-intet'-tilgang er, at du vil miste dataene for 'omdøbte felter'. Der vil også være tidspunkter, hvor du ønsker, at de nyligt migrerede data skal have specifikke værdier, der er forskellige fra de indbyggede .NET-standarder. Når du vil have mere kontrol over migreringen af ​​dine gamle data, er det en triviel øvelse at tilføje en tilpasset oversættelse, når du kan gøre det indbygget i kode:

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 er et datalager fyldt med nye data udfyldt på præcis den måde, du ønsker det - klar til at betjene funktionerne i din nye applikation. I modsætning hertil er det at forsøge ovenstående i en typisk RDBMS-løsning uden nedetid faktisk en magisk bedrift, der kan belønnes med 999 Stack Overflow-point og en personlig kondolence fra dets storkansler @JonSkeet 😃

Jeg håber, at dette tydeligt illustrerer forskellene mellem de to teknologier. I praksis vil du blive overrasket over de produktivitetsgevinster, der er muliggjort, når du ikke behøver at modellere din applikation, så den passer rundt om en ORM og en RDBMS og kan gemme objekter, som om det var hukommelse.

Det er altid en god idé at udsætte dig selv for nye teknologier, så hvis du ikke allerede har gjort det, inviterer jeg dig til at komme i gang med at udvikle med Redis i dag for at se fordelene for dig selv. For at komme i gang behøver du blot en forekomst af redis-serveren (ingen konfiguration nødvendig, bare unzip og kør) og den afhængighedsfrie ServiceStack's C# Redis Client, og du er klar til at gå!


  1. Jeg får fejlen Klasse 'Predis\Client' ikke fundet i Laravel 5.2

  2. Hvordan håndterer ConnectionMultiplexer afbrydelser?

  3. Hvornår skal man bruge Redis i stedet for MySQL til PHP-applikationer?

  4. MongoDB $dateFromParts