Redis 処理時間計測
Redisは永続化可能なインメモリデータベース。
連想配列、リスト、セットなどのデータ構造を扱えるキーバリューストア(KVS)。
ターンアラウンドタイム重視(オペレーション用途)、スケールアウト可能という位置付け。
Read/Writeが高速。キャッシュとして利用されることが多い。memcachedよりも機能は多い。
どのくらい高速なのか。大量データ登録の処理時間を計測してみよう!
図.データベースを分類する2つの軸、4つのエリア
<サーバー情報>
CPU = Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz
→2008年頃に発売されていたCPU?けっこう古いやつです。
メモリ = 8 GB
OS = Ubuntu 18.04.2 LTS
◆処理時間計測結果&簡単に考察を記載
<処理時間計測結果(1回目)>
10,000件 : 123ミリ秒
100,000件 : 908ミリ秒
200,000件 : 1秒 791ミリ秒
500,000件 : 4秒 176ミリ秒
1,000,000件 : 9秒 409ミリ秒
<処理時間計測結果(2回目)>
10,000件 : 111ミリ秒
100,000件 : 1秒 77ミリ秒
200,000件 : 2秒 213ミリ秒
500,000件 : 4秒 586ミリ秒
1,000,000件 : 8秒 948ミリ秒
Redis Desktop Manager でデータ確認。
→ 100万件データ登録されている。
1万件データ登録 111ミリ秒、123ミリ秒。
100万件データ登録 9秒 409ミリ秒、8秒 948ミリ秒。
パフォーマンス良いと思う。
今回は、Key=string Value=stringで大量データ登録を試しただけ。
次回は、他の処理も試したい。
あと、大量データを「参照する処理」の時間計測をしてみようとは思う。
<追記>
後になって、本当にそんなに高速なのかなと思ってしまった部分があったので。
データ登録処理が終了した直後に「登録済データ件数」を取得して、
その登録済データ件数を画面に出力するように、クライアント側アプリのプログラムを改造してみたが。
ちゃんと全件がデータ登録できていたし、本当に高速だった。
<処理時間計測結果(3回目)>
10,000件 : 133ミリ秒
100,000件 : 1秒 133ミリ秒
200,000件 : 1秒 950ミリ秒
500,000件 : 4秒 698ミリ秒
1,000,000件 : 9秒 23ミリ秒
<クライアント側で使用したアプリについて>
Redis処理時間計測のためにクライアント側のアプリを作成してみた。
Windows フォーム アプリケーション(.NET Framework 4.7.2)、言語は C# 。
StackExchange.Redis というライブラリを使用。
プログラムは参考までに。
◆処理時間計測に使用したC#プログラム
https://github.com/masawan/RedisLibrarygithub.com
FrmMain.cs
using StackExchange.Redis; using Redis.DataAccess.Client; using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Threading.Tasks; namespace RedisClient { public partial class FrmMain : Form { public FrmMain() { InitializeComponent(); } private static bool processingFlg = false; private async void btnExecSet_Click(object sender, EventArgs e) { processingFlg = true; RedisConnection conn = null; this.ChangeControlEnabled(false); txtResult.Text = string.Empty; try { conn = new RedisConnection(); conn.Open(); RedisCommand cmd = new RedisCommand(conn); // (´・ω・`)ノ「令和(reiwa)」 //フォームが無反応にならないようにするため //OutputProcessTime_Setの処理は、 //別スレッドで実行して処理終了まで待つ(await)。 await OutputProcessTime_Set(cmd, 10000); await OutputProcessTime_Set(cmd, 100000); await OutputProcessTime_Set(cmd, 200000); await OutputProcessTime_Set(cmd, 500000); await OutputProcessTime_Set(cmd,1000000); } catch (Exception ex) { MessageBox.Show(ex.Message, "システムエラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { if(conn!=null) { conn.Close(); } this.ChangeControlEnabled(true); txtResult.Select(this.txtResult.Text.Length, 0); processingFlg = false; } } private void ChangeControlEnabled(bool value) { btnExecSet.Enabled = value; txtResult.Enabled = value; } private Task OutputProcessTime_Set(RedisCommand cmd, int dataCount) { return Task.Run(() => { const char HALF_SPACE = ' '; Dictionary<RedisKey, RedisValue> dic; var sw = new System.Diagnostics.Stopwatch(); TimeSpan ts; //データベース初期化 cmd.FlushDatabases(); //登録データ取得(データ件数分) dic = FrmMain.getInsertData(dataCount); // (´・ω・`)ノ Let's Go!! //処理時間計測 スタート sw.Restart(); //Redis Set実行 cmd.Set(dic); //処理時間計測 ストップ sw.Stop(); //処理時間を出力 ts = sw.Elapsed; long dbSize = cmd.DbSize(); //登録済のデータ件数を取得 string outputText = $"{txtResult.Text}" + $"{dataCount.ToString().PadLeft(7, HALF_SPACE)}件" + $"{HALF_SPACE}" + $"処理時間 = " + $"{ts.Minutes.ToString().PadLeft(2, HALF_SPACE)}[m] " + $"{ts.Seconds.ToString().PadLeft(2, HALF_SPACE)}[s] " + $"{ts.Milliseconds.ToString().PadLeft(3, HALF_SPACE)}[ms]" + $"{HALF_SPACE}" + $"(登録データ件数 = " + $"{dbSize.ToString().PadLeft(7, HALF_SPACE)}件)" + $"\r\n"; this.SetText(txtResult, outputText); }); } private void SetText(TextBox textBox, string text) { if (textBox.IsDisposed) return; if (textBox.InvokeRequired) { this.Invoke((MethodInvoker) delegate { SetText(textBox, text); }); } else { textBox.Text = text; } } private static Dictionary<RedisKey,RedisValue>getInsertData(int dataCount) { var dic = new Dictionary<RedisKey, RedisValue>(dataCount); var sbKey = new StringBuilder(); var sbValue = new StringBuilder(); for (int count = 1; count <= dataCount; ++count) { sbKey.Clear(); sbKey.Append("key"); sbKey.Append(count.ToString().PadLeft(7, '0')); sbValue.Clear(); sbValue.Append("value"); sbValue.Append(count.ToString().PadLeft(7, '0')); dic.Add(sbKey.ToString(), sbValue.ToString()); } return dic; } private void FrmMain_FormClosing(object sender, FormClosingEventArgs e) { if(processingFlg) { MessageBox.Show("処理実行中のため、画面を閉じることはできません。" ,"情報" , MessageBoxButtons.OK , MessageBoxIcon.Information); e.Cancel = true; } } } }
RedisConnection.cs
using StackExchange.Redis; using Redis.DataAccess.Client.Properties; namespace Redis.DataAccess.Client { public class RedisConnection { private static ConnectionMultiplexer redisStore; private static IDatabase db; private const string CONFIGURATION_OPTIONS = "allowAdmin=true"; private static readonly string configurationStringsDefault = Settings.Default.IPAddress + ":" + Settings.Default.PortNo.ToString() + "," + CONFIGURATION_OPTIONS; public RedisConnection() { } public void Open() { redisStore = ConnectionMultiplexer.Connect(configurationStringsDefault); db = redisStore.GetDatabase(); } public void Close() { if(redisStore!=null) { redisStore.Close(); redisStore.Dispose(); } } public IServer Server { get => redisStore.GetServer(Settings.Default.IPAddress, Settings.Default.PortNo); } public string ConfigurationStrings { get => configurationStringsDefault; } public IDatabase Database { get => db; } public ConnectionMultiplexer RedisStore { get => redisStore; } } }
RedisCommand.cs
using StackExchange.Redis; using System.Collections.Generic; using System.Threading.Tasks; namespace Redis.DataAccess.Client { public class RedisCommand { private static IDatabase db { get; set; } private static IServer server { get; set; } public RedisCommand(RedisConnection conn) { server = conn.Server; db = conn.Database; } public long DbSize() { return server.DatabaseSize(); } public void FlushDatabases() { server.FlushAllDatabases(); } public bool Set(RedisKey key, RedisValue value) { return db.StringSet(key, value); } public void Set(Dictionary<RedisKey, RedisValue> dic) { var setTasks = new List<Task>(); foreach (KeyValuePair<RedisKey, RedisValue> kvp in dic) { Task<bool> setAsync = db.StringSetAsync(kvp.Key, kvp.Value); setTasks.Add(setAsync); } Task[] tasks = setTasks.ToArray(); setTasks.Clear(); setTasks.TrimExcess(); // (´・ω・`)ノ Clear! and TrimExcess ! Task.WaitAll(tasks); } } }
◆メモ「StackExchange.Redis pipelining」
optimization - Pipelining vs Batching in Stackexchange.Redis - Stack Overflow
Redis for .NET Developers – Redis Pipeline Batching | Taswar BhattiTaswar Bhatti