sql >> Database >  >> NoSQL >> Redis

Gegevensmigraties met Redis

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 in long en double velden
  • Gewijzigd tagverzamelingstype van een List naar een HashSet
  • Een sterk getypte BlogPostComment typ in een losjes getypte string Dictionary
  • 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!


  1. Hoe u uw databaseservers kunt bewaken met ClusterControl CLI

  2. $skip en $limit in aggregatieframework

  3. Verschil tussen het opslaan van een ObjectId en zijn tekenreeksvorm, in MongoDB

  4. Hoe Redis-sleutels in serie met Java te repareren