SQLServerやPostgreSQL(リレーショナル データベース)をメインに扱ってきた私が、
Azure Cosmos DB(非リレーショナル データベース)を使った際に苦戦したことや違いについて記述していく。
更新する処理の速度が思った以上に出ない
.NET COREでデータ件数分のクエリを発行してデータを更新を行いたいことがありました。
100万件 ~ 1,000万件のデータに対して更新クエリを発行する必要がありました。
速度が思った以上に出ない。1件当たり約100ms掛かり全体で数時間以上の時間が掛ってしまいます。
公式ドキュメントから複数のクエリを一括で発行する方法がないかと調べてみたものの見つかりませんでした。
このままでは実用するには厳しいため、何が原因なのかを調査しました。
Geometry型を含むときに遅い
Geometry型を含む/含まないで処理時間が大きく違うことがわかりました。
しかし、Geometry型を使うことは必須事項であるためこれを省くことができません。
単純にクエリを効率化するだけでは解決できないものだと考え、
プログラム側で効率化を図るため処理の並列化を行おうと考えました。
Geometry型あり | 約100ms |
Geometry型なし | 約 5ms |
処理の並列化
並列化をするにあたって、Azure Cosmos DBには要求ユニット(RU)というデータベース操作を実行するために必要な CPU、IOPS、メモリなどのシステム リソースを抽象化したリソースがあります。
要求ユニットを多く確保すればその分大きな処理にも耐えられるデータベースとなります。ただし、リソースを多く確保すればもちろん予算コストも比例して大きくなります。
並列化するにあたって、スレッドの管理も必要になります。
スレッド数をやみくもに増やせばAzure Cosmos DBに設定している要求ユニット数を超えてしまします。
そのため、Taskを使ってスレッド数を管理しながら処理を行いました。
using var dataReader = await command.ExecuteReaderAsync();
var queue = new ConcurrentQueue<Tuple<string>>();
var IsEnd = false;
var taskList = new List<Task>();
for (var i = 0; i < 10; i++)
{
taskList.Add(Task.Factory.StartNew(() =>
{
while (!IsEnd || queue.Count > 0)
{
while (queue.TryDequeue(out Tuple<string, string> data))
{
if (data != null)
{
QueryDefinition query = new QueryDefinition($"SELECT * FROM u WHERE u.id = @data")
.WithParameter("@data", data.Item1);
var userdata = container.GetItemQueryIterator<UserData>(query).ReadNextAsync().Result.FirstOrDefault();
userdata.name = data.Item2;
container.ReplaceItemAsync<medical>(userdata, userdata.Id, new Microsoft.Azure.Cosmos.PartitionKey(userdata.Tenant_id));
}
}
}
}));
}
while (dataReader.Read())
{
queue.Enqueue(new(dataReader.GetString(0), dataReader.GetString(1)));
}
IsEnd = true;
Task.WaitAll(taskList.ToArray());