このページでは、典型的な例を紹介し、Redisやその他のNoSQLスキーマレスデータストアを使用した場合に、典型的なデータ移行がどれほど簡単になるかを示します。
すべてのRedisブログアプリケーションページ#
- Redisを使用したNoSQLデータベースの設計
- Redisやその他のスキーマレスNoSQLデータストアを使用した痛みのないデータ移行
スキーマのないNoSQLデータストアとRedisを使用した痛みのないデータ移行#
新規の開発 RDBMSバックエンドを利用するグリーンフィールドデータベースシステムは、ほとんど問題のないエクスペリエンスです。システムが稼働する前に、アプリケーションデータベース全体を削除し、新しいスキーマに適合するテストデータを作成してデータを取り込む自動化されたDDLスクリプトを使用してスキーマを再作成することで、スキーマを簡単に変更できます。
ITライフの本当の問題は、最初の展開とシステムの稼働後に発生します。その時点で、データベースを削除して最初から再作成するオプションはなくなりました。運が良ければ、古いスキーマから新しいスキーマに移行するために必要なDDLステートメントを自動的に推測できるスクリプトが用意されています。ただし、スキーマに大幅な変更を加えると、深夜、ダウンタイム、および新しいdbスキーマへの移行を成功させるための重要な労力が必要になる可能性があります。
このプロセスは、スキーマのないデータストアではそれほど苦痛ではありません。実際、ほとんどの場合、フィールドを追加および削除するだけでは、フィールドはまったく存在しません。データストアにスキーマの本質的な詳細を理解させないことで、インフラストラクチャレベルの問題ではなくなり、必要に応じてアプリケーションロジックで簡単に処理できるようになります。
メンテナンスフリー、スキーマレス、邪魔にならないことは、Redisとその操作に組み込まれている基本的な設計品質です。たとえば、最近のBlogPostsのリストをクエリすると、空のリストに対して同じ結果が返されます。 空のRedisデータベースの場合と同じように --0件の結果。 Redisの値はバイナリセーフな文字列であるため、必要なものをすべて格納できます。最も重要なことは、これは、すべてのRedis操作が、DDLなどの「中間言語」を必要とせずにすべてのアプリケーションタイプをサポートできることを意味します。何を期待するかの厳密なスキーマ。事前の初期化がなくても、コードはメモリ内のコレクションであるかのように、自然にRedisデータストアと直接通信できます。
実際に何が達成できるかを説明するために、スキーマの変更を処理する2つの異なる戦略を実行します。
- 何もしないアプローチ-フィールドの追加、削除、およびフィールドタイプの非破壊的な変更が自動的に処理されます。
- カスタム翻訳の使用-アプリケーションレベルのロジックを使用して、古いタイプと新しいタイプの間の翻訳をカスタマイズします。
この例の完全な実行可能なソースコードは、こちらから入手できます。
サンプルコード#
典型的な移行シナリオを示すために、BlogPost
を使用しています。 前のページで定義したタイプを使用して、根本的に異なるNew.BlogPost
に投影します。 タイプ。古いタイプと新しいタイプの完全な定義を以下に示します。
古いスキーマ#
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; }
}
新しいスキーマ#
「新しいバージョン」には、通常のアプリ開発で遭遇する可能性のあるほとんどの変更が含まれています:
- フィールドの追加、削除、名前の変更
-
int
の非破壊的な変更long
に およびdouble
フィールド - タグコレクションタイプを
List
から変更しましたHashSet
に - 強く型付けされた
BlogPostComment
を変更しました 緩く型付けされた文字列Dictionary
に入力します - 新しい
enum
を導入しました タイプ - null許容の計算フィールドを追加しました
新しいスキーマタイプ#
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。何もしないアプローチ-古いデータを新しいタイプで使用する#
信じがたいことですが、特別な努力をしなくても、実際には変更が行われていないふりをすることができます。 古いデータを見ながら新しいタイプに自由にアクセスできます。これは、新しいフィールドタイプで非破壊的な変更がある場合(つまり、情報が失われない場合)に可能です。以下の例では、前の例のリポジトリを使用して、Redisに古いタイプのテストデータを入力します。何も起こらなかったかのように、新しいタイプを使用して古いデータを読み取ることができます:
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。カスタム変換を使用して、アプリケーションロジックを使用してデータを移行する#
上記の「何もしない」アプローチのいくつかの欠点は、「名前が変更されたフィールド」のデータが失われることです。また、新しく移行されたデータに、.NETの組み込みのデフォルトとは異なる特定の値を持たせたい場合もあります。古いデータの移行をより細かく制御したい場合、カスタム翻訳を追加することは、コードでネイティブに実行できる場合は簡単な作業です。
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
}
]
*/
}
最終的には、新しいデータが希望どおりに入力されたデータストアになり、新しいアプリケーションの機能を提供できるようになります。対照的に、ダウンタイムなしで典型的なRDBMSソリューションで上記を試みることは、事実上、999スタックオーバーフローポイントとその壮大な首相@JonSkeetからの個人的な哀悼の意によって報われる魔法の偉業です😃
これが2つのテクノロジーの違いを明確に示していることを願っています。実際には、ORMやRDBMSに合わせてアプリケーションをモデル化する必要がなく、メモリのようにオブジェクトを保存できる場合に、生産性が向上することに驚かれることでしょう。
新しいテクノロジーに触れることは常に良い考えです。まだ行っていない場合は、今日からRedisの開発を開始して、自分にとってのメリットを確認することをお勧めします。開始するために必要なのは、redis-serverのインスタンス(構成は不要で、解凍して実行するだけです)と依存関係のないServiceStackのC#Redisクライアントであり、準備ができています!