sql >> Base de Datos >  >> NoSQL >> Redis

Migraciones de datos con Redis

Esta página se ejecuta a través de un ejemplo típico para mostrar cuán sencillas pueden ser las migraciones de datos típicas cuando se usa Redis y otros almacenes de datos sin esquema NoSQL.

Todas las páginas de la aplicación Redis Blog #

  • Diseño de una base de datos NoSQL con Redis
  • Migraciones de datos sin complicaciones con Redis y otros almacenes de datos NoSQL sin esquema

Migraciones de datos sin dolor con almacenes de datos NoSQL sin esquema y Redis #

Desarrollando nuevo Los sistemas de bases de datos completamente nuevos que utilizan un back-end RDBMS son en su mayoría una experiencia sin problemas. Antes de que el sistema esté activo, puede modificar fácilmente un esquema destruyendo toda la base de datos de la aplicación y volviéndola a crear con scripts DDL automatizados que la crearán y la completarán con datos de prueba que se ajusten a su nuevo esquema.

Los verdaderos problemas en su vida de TI ocurren después de su primera implementación y su sistema se activa. En ese momento, ya no tiene la opción de destruir la base de datos y volver a crearla desde cero. Si tiene suerte, tiene una secuencia de comandos que puede inferir automáticamente las declaraciones DDL requeridas para migrar de su esquema anterior a uno nuevo. Sin embargo, es probable que cualquier cambio significativo en su esquema implique horas de la noche, tiempo de inactividad y una cantidad de esfuerzo no trivial para garantizar una migración exitosa al nuevo esquema de base de datos.

Este proceso es mucho menos doloroso con almacenes de datos sin esquema. De hecho, en la mayoría de los casos, cuando solo agrega y elimina campos, no existe en absoluto. Al no tener su almacén de datos que comprenda los detalles intrínsecos de su esquema, significa que ya no es un problema de nivel de infraestructura y puede ser manejado fácilmente por la lógica de la aplicación si es necesario.

Ser libre de mantenimiento, sin esquemas y no intrusivo son cualidades de diseño fundamentales integradas en Redis y sus operaciones. Por ejemplo, consultar una lista de publicaciones de blog recientes arroja el mismo resultado para una lista vacía como lo haría en una base de datos de Redis vacía - 0 resultados. Como los valores en Redis son cadenas binarias seguras, puede almacenar lo que desee en ellas y, lo que es más importante, por extensión, esto significa que todas las operaciones de Redis pueden admitir todos sus tipos de aplicaciones sin necesidad de un "lenguaje intermedio" como DDL para proporcionar un esquema rígido de qué esperar. Sin ninguna inicialización previa, su código puede comunicarse directamente con un almacén de datos de Redis de forma natural como si fuera una colección en memoria.

Para ilustrar lo que se puede lograr en la práctica, analizaré dos estrategias diferentes para manejar los cambios de esquema.

  • El enfoque de no hacer nada:donde la adición, eliminación de campos y el cambio no destructivo de los tipos de campo se manejan automáticamente.
  • Uso de una traducción personalizada:uso de lógica de nivel de aplicación para personalizar la traducción entre los tipos antiguo y nuevo.

El código fuente ejecutable completo para este ejemplo está disponible aquí.

Código de ejemplo n.°

Para demostrar un escenario de migración típico, estoy usando BlogPost tipo definido en la página anterior para proyectarlo a un New.BlogPost fundamentalmente diferente tipo. La definición completa de los tipos antiguo y nuevo se muestra a continuación:

El viejo esquema #

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

El nuevo esquema #

La 'nueva versión' contiene la mayoría de los cambios que probablemente encontrará en el desarrollo normal de aplicaciones:

  • Campos agregados, eliminados y renombrados
  • Cambio no destructivo de int en long y double campos
  • Cambió el tipo de colección de etiquetas de una List a un HashSet
  • Cambió un BlogPostComment fuertemente tipado teclee en una cadena poco escrita Dictionary
  • Introdujo un nuevo enum escribir
  • Se agregó un campo calculado anulable

Nuevos 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. El enfoque de no hacer nada:usar los datos antiguos con los nuevos tipos #

Aunque es difícil de creer, sin ningún esfuerzo extra puedes fingir que no se hizo ningún cambio y acceda libremente a nuevos tipos mirando datos antiguos. Esto es posible cuando hay cambios no destructivos (es decir, sin pérdida de información) con nuevos tipos de campo. El siguiente ejemplo usa el repositorio del ejemplo anterior para llenar Redis con datos de prueba de los tipos antiguos. Como si nada hubiera pasado, puedes leer los datos antiguos usando el nuevo 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 una traducción personalizada para migrar datos usando la lógica de la aplicación #

Algunos inconvenientes con el enfoque anterior de 'no hacer nada' es que perderá los datos de los 'campos renombrados'. También habrá momentos en los que desee que los datos recién migrados tengan valores específicos que sean diferentes de los valores predeterminados integrados de .NET. Cuando desea tener más control sobre la migración de sus datos antiguos, agregar una traducción personalizada es un ejercicio trivial cuando puede hacerlo de forma nativa en el 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
        }
    ]

     */
}

El resultado final es un almacén de datos repleto de nuevos datos exactamente de la manera que usted lo desea, listo para servir funciones de su nueva aplicación. Por el contrario, intentar lo anterior en una solución RDBMS típica sin ningún tiempo de inactividad es efectivamente una proeza mágica que se recompensa con 999 puntos de desbordamiento de pila y una condolencia personal de su gran canciller @JonSkeet 😃

Espero que esto ilustre claramente las diferencias entre las dos tecnologías. En la práctica, se sorprenderá de las ganancias de productividad que son posibles cuando no tiene que modelar su aplicación para que se ajuste a un ORM y un RDBMS y puede guardar objetos como si fuera memoria.

Siempre es una buena idea exponerse a nuevas tecnologías, así que si aún no lo ha hecho, lo invito a comenzar a desarrollar con Redis hoy mismo para ver los beneficios por sí mismo. Para comenzar, todo lo que necesita es una instancia del servidor redis (no se requiere configuración, solo descomprima y ejecute) y el cliente C# Redis de ServiceStack sin dependencias, ¡y ya está listo!


No