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

Migrazioni dati con Redis

Questa pagina mostra un esempio tipico per mostrare quanto possano essere indolori le tipiche migrazioni dei dati quando si utilizzano Redis e altri archivi dati NoSQL senza schema.

Tutte le pagine dell'applicazione Redis Blog #

  • Progettazione di un database NoSQL utilizzando Redis
  • Migrazioni di dati indolori tramite Redis e altri datastore NoSQL senza schema

Migrazioni di dati indolori con datastore NoSQL senza schema e Redis #

Sviluppare nuovo i sistemi di database greenfield che utilizzano un back-end RDBMS sono per lo più un'esperienza senza problemi. Prima che il sistema sia attivo, puoi modificare facilmente uno schema modificando l'intero database dell'applicazione e ricreandolo con script DDL automatizzati che lo creeranno e lo compileranno con dati di test che si adattano al tuo nuovo schema.

I veri problemi nella tua vita IT si verificano dopo la tua prima implementazione e il tuo sistema diventa operativo. A quel punto non hai più la possibilità di modificare il database e ricrearlo da zero. Se sei fortunato, hai uno script in atto che può dedurre automaticamente le istruzioni DDL richieste per migrare dal tuo vecchio schema a quello nuovo. Tuttavia, è probabile che qualsiasi modifica significativa allo schema comporti la notte tarda, tempi di inattività e uno sforzo non banale per garantire una migrazione corretta al nuovo schema db.

Questo processo è molto meno doloroso con archivi di dati senza schema. In effetti, nella maggior parte dei casi, quando aggiungi e rimuovi campi, non esiste affatto. Non facendo in modo che il tuo datastore comprenda i dettagli intrinseci del tuo schema, significa che non è più un problema a livello di infrastruttura e può essere facilmente gestito dalla logica dell'applicazione, se necessario.

Essere esenti da manutenzione, senza schemi e non invadenti sono qualità di progettazione fondamentali integrate in Redis e nelle sue operazioni. Ad esempio, l'interrogazione di un elenco di BlogPost recenti restituisce lo stesso risultato per un elenco vuoto come in un database Redis vuoto - 0 risultati. Poiché i valori in Redis sono stringhe a sicurezza binaria, puoi archiviare tutto ciò che desideri al loro interno e, soprattutto, per estensione, ciò significa che tutte le operazioni Redis possono supportare tutti i tuoi tipi di applicazione senza bisogno di un "linguaggio intermedio" come DDL per fornire un schema rigido di cosa aspettarsi. Senza alcuna inizializzazione preventiva, il tuo codice può comunicare direttamente con un datastore Redis in modo naturale come se fosse una raccolta in memoria.

Per illustrare cosa si può ottenere in pratica, analizzerò due diverse strategie di gestione delle modifiche allo schema.

  • L'approccio "non fare nulla", in cui l'aggiunta, la rimozione di campi e la modifica non distruttiva dei tipi di campo vengono gestite automaticamente.
  • Utilizzo di una traduzione personalizzata:utilizzo della logica a livello di applicazione per personalizzare la traduzione tra il vecchio e il nuovo tipo.

Il codice sorgente completo per questo esempio è disponibile qui.

Codice esempio n.

Per dimostrare uno scenario di migrazione tipico, sto utilizzando il BlogPost tipo definito nella pagina precedente per proiettarlo in un New.BlogPost fondamentalmente diverso genere. La definizione completa del vecchio e del nuovo tipo è mostrata di seguito:

Il vecchio 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; }
}

Il nuovo schema #

La "nuova versione" contiene la maggior parte delle modifiche che potresti incontrare nel normale sviluppo di app:

  • Campi aggiunti, rimossi e rinominati
  • Modifica non distruttiva di int in long e double campi
  • Tipo di raccolta tag modificato da un List a un HashSet
  • Cambiato un BlogPostComment fortemente tipizzato digita in una stringa digitata liberamente Dictionary
  • Introdotto un nuovo enum digita
  • Aggiunto un campo calcolato nullable

Nuovi tipi di schema #

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'approccio del non fare nulla:utilizzare i vecchi dati con i nuovi tipi #

Sebbene sia difficile da credere, senza alcuno sforzo aggiuntivo puoi semplicemente fingere che nessun cambiamento sia stato effettivamente apportato e accedere liberamente a nuovi tipi guardando i vecchi dati. Ciò è possibile quando sono presenti modifiche non distruttive (ovvero nessuna perdita di informazioni) con nuovi tipi di campo. L'esempio seguente usa il repository dell'esempio precedente per popolare Redis con i dati di test dei vecchi tipi. Proprio come se nulla fosse, puoi leggere i vecchi dati usando il nuovo tipo:

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. Utilizzo di una traduzione personalizzata per migrare i dati utilizzando la logica dell'applicazione #

Alcuni svantaggi dell'approccio "non fare nulla" di cui sopra è che perderai i dati dei "campi rinominati". Ci saranno anche momenti in cui si desidera che i dati appena migrati abbiano valori specifici diversi dai valori predefiniti predefiniti di .NET. Quando desideri un maggiore controllo sulla migrazione dei tuoi vecchi dati, aggiungere una traduzione personalizzata è un esercizio banale quando puoi farlo in modo nativo nel codice:

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

     */
}

Il risultato finale è un datastore pieno di nuovi dati popolati esattamente nel modo desiderato, pronto per servire le funzionalità della tua nuova applicazione. Al contrario, tentare quanto sopra in una tipica soluzione RDBMS senza alcun tempo di inattività è effettivamente un'impresa magica che è ricompensata con 999 punti Stack Overflow e le condoglianze personali del suo gran cancelliere @JonSkeet 😃

Spero che questo illustri chiaramente le differenze tra le due tecnologie. In pratica rimarrai stupito dai guadagni di produttività resi possibili quando non devi modellare la tua applicazione per adattarla a un ORM e un RDBMS e puoi salvare oggetti come se fosse memoria.

È sempre una buona idea esporsi alle nuove tecnologie, quindi se non l'hai già fatto, ti invito a iniziare a sviluppare con Redis oggi stesso per vedere i vantaggi di persona. Per iniziare tutto ciò di cui hai bisogno è un'istanza del server redis (nessuna configurazione richiesta, basta decomprimere ed eseguire) e il client Redis C# di ServiceStack senza dipendenze e sei pronto per partire!


No