sql >> Databasteknik >  >> NoSQL >> Redis

Datamigreringar med Redis

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 till long och double fält
  • Ändrad taggsamlingstyp från en List till en HashSet
  • Ändrade en starkt skriven BlogPostComment skriv i en löst skriven sträng Dictionary
  • 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å!


  1. mongodb misslyckades:fel vid anslutning till db-server:inga servrar som kan nås

  2. Arbetar du med kapslade objekt i Redis?

  3. Hur ställer jag in en timeout för en Mongoose-fråga?

  4. Samtidighet i gopkg.in/mgo.v2 (Mongo, Go)