Redis
 sql >> Base de Dados >  >> NoSQL >> Redis

Migrações de dados com Redis


Esta página mostra um exemplo típico para mostrar como as migrações de dados típicas podem ser simples ao usar o Redis e outros armazenamentos de dados sem esquema NoSQL.

Todas as páginas do aplicativo Redis Blog #

  • Projetando um banco de dados NoSQL usando Redis
  • Migrações de dados indolor usando Redis e outros armazenamentos de dados NoSQL sem esquema

Migrações de dados indolor com datastores NoSQL e Redis sem esquema #


Desenvolvendo novo sistemas de banco de dados greenfield utilizando um back-end RDBMS é principalmente uma experiência livre de problemas. Antes que o sistema esteja ativo, você pode facilmente modificar um esquema nuking todo o banco de dados do aplicativo e recriá-lo com scripts DDL automatizados que o criarão e preencherão com dados de teste que se ajustem ao seu novo esquema.

Os problemas reais em sua vida de TI acontecem após sua primeira implantação e seu sistema entra em operação. Nesse ponto, você não tem mais a opção de destruir o banco de dados e recriá-lo do zero. Se tiver sorte, você tem um script que pode inferir automaticamente as instruções DDL necessárias para migrar do esquema antigo para o novo. No entanto, quaisquer alterações significativas em seu esquema provavelmente envolverão madrugadas, tempo de inatividade e uma quantidade não trivial de esforço para garantir uma migração bem-sucedida para o novo esquema de banco de dados.

Esse processo é muito menos doloroso com armazenamentos de dados sem esquema. Na verdade, na maioria dos casos, quando você está apenas adicionando e removendo campos, ele não existe. Ao não fazer com que seu armazenamento de dados entenda os detalhes intrínsecos do seu esquema, isso significa que não é mais um problema de nível de infraestrutura e pode ser facilmente tratado pela lógica do aplicativo, se necessário.

Ser livre de manutenção, sem esquema e não intrusivo são qualidades de design fundamentais incorporadas ao Redis e suas operações. Por exemplo, consultar uma lista de BlogPosts recentes retorna o mesmo resultado para uma lista vazia como seria em um banco de dados Redis vazio - 0 resultados. Como os valores no Redis são strings seguras para binários, você pode armazenar o que quiser neles e, mais importante, por extensão, isso significa que todas as operações do Redis podem suportar todos os seus tipos de aplicativos sem precisar de uma 'linguagem intermediária' como DDL para fornecer um esquema rígido do que esperar. Sem qualquer inicialização prévia, seu código pode se comunicar diretamente com um armazenamento de dados Redis naturalmente, como se fosse uma coleção na memória.

Para ilustrar o que pode ser alcançado na prática, analisarei duas estratégias diferentes para lidar com mudanças de esquema.
  • A abordagem do-nothing - onde adicionar, remover campos e alterações não destrutivas de tipos de campo são tratadas automaticamente.
  • Usando uma tradução personalizada - usando lógica de nível de aplicativo para personalizar a tradução entre os tipos antigo e novo.

O código-fonte executável completo para este exemplo está disponível aqui.

Exemplo de código #


Para demonstrar um cenário de migração típico, estou usando o BlogPost tipo definido na página anterior para projetá-lo para um New.BlogPost fundamentalmente diferente tipo. A definição completa dos tipos antigos e novos são mostradas abaixo:

O esquema antigo #

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

O novo esquema #


A 'nova versão' contém a maioria das alterações que você provavelmente encontrará no desenvolvimento normal de aplicativos:
  • Campos adicionados, removidos e renomeados
  • Alteração não destrutiva de int em long e double campos
  • Alterou o tipo de coleção de tags de uma List para um HashSet
  • Alterou um BlogPostComment fortemente tipado digite em uma string de tipo livre Dictionary
  • Introduziu um novo enum tipo
  • Adicionado um campo calculado que permite valor nulo

Novos tipos de esquema #

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. A abordagem do-nothing - usando os dados antigos com os novos tipos #


Embora seja difícil de acreditar, sem nenhum esforço extra, você pode simplesmente fingir que nenhuma mudança foi realmente feita e acesse livremente novos tipos de dados antigos. Isso é possível quando há alterações não destrutivas (ou seja, sem perda de informações) com novos tipos de campo. O exemplo abaixo usa o repositório do exemplo anterior para preencher o Redis com dados de teste dos tipos antigos. Assim como se nada tivesse acontecido, você pode ler os dados antigos usando o novo 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. Usando uma tradução personalizada para migrar dados usando a lógica do aplicativo #


Algumas desvantagens com a abordagem 'não fazer nada' acima é que você perderá os dados de 'campos renomeados'. Também haverá momentos em que você desejará que os dados recém-migrados tenham valores específicos que sejam diferentes dos padrões internos do .NET. Quando você deseja mais controle sobre a migração de seus dados antigos, adicionar uma tradução personalizada é um exercício trivial quando você pode fazê-lo nativamente no código:
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
        }
    ]

     */
}

O resultado final é um armazenamento de dados preenchido com novos dados preenchidos exatamente da maneira que você deseja - pronto para servir os recursos do seu novo aplicativo. Em contraste, tentar o acima em uma solução RDBMS típica sem qualquer tempo de inatividade é efetivamente uma façanha mágica que é recompensada por 999 pontos Stack Overflow e uma condolência pessoal de seu grande chanceler @JonSkeet 😃

Espero que isso ilustre claramente as diferenças entre as duas tecnologias. Na prática, você ficará surpreso com os ganhos de produtividade possibilitados quando você não precisa modelar seu aplicativo para caber em um ORM e um RDBMS e pode salvar objetos como se fosse memória.

É sempre uma boa ideia se expor a novas tecnologias, portanto, se você ainda não o fez, convido você a começar a desenvolver com o Redis hoje mesmo para ver os benefícios por si mesmo. Para começar, tudo o que você precisa é de uma instância do redis-server (sem necessidade de configuração, apenas descompacte e execute) e o cliente C# Redis do ServiceStack sem dependência e você está pronto para começar!

No