バッチ処理を50倍高速化する

DBデータをストリーム処理するを応用するとバッチ処理を高速化できます。SQL Serverに1億件のテストデータを高速に作成するの結果からすると、ストアドプロシージャで作成する場合に比べて50倍高速に処理できることになります。

メインフレーム上の基幹系のバッチの多くはファイルを読み込み、加工処理、出力する処理を繰り返して行います。これをRDBMSでファイルの代わりにテーブルを利用するイメージになります。

さらに、LINQ対応することでLINQで用意されたいろいろな仕組みを利用することができます。以下はAdventureWorksデータベースのSalesOrderDetailを顧客単位に集約して値引きを計算する処理です。AsParallelで並列化(スケールアウト)も簡単にできます。12万超のデータを処理するのに手元のPCで7.8秒です。100万件でも1分ちょいの処理時間ということになります。

var sw = new Stopwatch();
sw.Start();
using (var cnct = new SqlConnection(config.ConnectionString))
{
    cnct.Open();
    var cmd = cnct.CreateCommand();
    cmd.CommandText = @"SELECT * FROM Sales.SalesOrderDetail INNER JOIN
            Sales.SalesOrderHeader ON Sales.SalesOrderDetail.SalesOrderID = Sales.SalesOrderHeader.SalesOrderID Order by CustomerID, OrderDate";

    cmd.ExecuteReader().AsEnumerable<SalesOrderDetailJoinSalesOrderHeader>()
        .GroupByCustomer()                      //顧客単位でまとめる
        .AsParallel()                           //並列化
        .SelectMany(x=>CalcDiscount(x))         //値引き額を計算
        .BulkCopy("[UpdateData]");
}
sw.Stop();
Console.WriteLine("ElapsedMilliseconds:{0}", sw.ElapsedMilliseconds);
Console.ReadLine();

LINQを使う場合の注意として、Linq to ObjectのGroup byなど全てデータを読み出してしまうような処理は避ける必要があります。メモリが爆発してしまいます。Group byのような処理は以下のようにソートしたデータを自前でグルーピングすればOKです。

public static IEnumerable<List<SalesOrderDetailJoinSalesOrderHeader>> GroupByCustomer(
                                      this IEnumerable<SalesOrderDetailJoinSalesOrderHeader> source)
{
    SalesOrderDetailJoinSalesOrderHeader predata = null;
    var list = new List<SalesOrderDetailJoinSalesOrderHeader>();
    foreach (var item in source)
    {
        if (predata != null && predata.CustomerID != item.CustomerID)
        {
            yield return list;
            list = new List<SalesOrderDetailJoinSalesOrderHeader>();
        }
        list.Add(item);
        predata = item;
    }
    if (list.Count > 0) yield return list;
}

static List<UpdateData> CalcDiscount(List<SalesOrderDetailJoinSalesOrderHeader> source)
{
    var total = source.Sum(x => x.LineTotal);
    var discount = total > 100 ? 1 : 0;
    var list = new List<UpdateData>(); 
    foreach (var data in source)
    {
        count++;
        var item = new UpdateData();
        item.SalesOrderID = data.SalesOrderID;
        item.SalesOrderDetailID = data.SalesOrderDetailID;
        item.UnitPriceDiscount = data.UnitPriceDiscount + discount;
        list.Add(item);
    }
    return list;
}