業務でサーバサイドやバッチ系アプリを作成する際に重要なのがパフォーマンス。最初はテストを通すために3~5件程度をテストデータで準備するので留意しないのですが、結合テスト以降に本番データを投入する時に言われてしまう「遅い」「1日流し続けても終わらない」
今回サーバサイドを作る上で実際に動作の遅さを目の当たりにしてしまったので、どう対応したかをメモの意味も含めて残しておきたいと思います。
目次
ToList ではなくToArrayを使う
これは劇的な改善にはなりませんでしたが、ToListよりもToArrayを使った方が性能が良いです。
注意点としてはDBの結果をループで回す時に終了条件を件数で回す時はListと配列では定義がちょっと違うのですがそこだけ気をつければどちらでも今後の処理に対応できたりします。
EntityFrameWork拡張ツールをインストールしてBulkInsertを用いる
この EntityFrameWork Coreの拡張ツールとしてZ.EntityFramework.Extensions.EFCoreがありますが、これを使用すればDBの保存形式にBulkInsertが使用できます。参考にしたのは下記の記事です。→ 2022/2/10確認したらリンク先が消滅していました
SQL Bulk Insertオペレーション – Entity FrameworkのBulkInsertよりずっと速くする方法
これも試したのですが、単体のテーブルの登録には良さそうですが、SaveChanges()で子テーブルの挿入も実現したい場合は子テーブルの登録ができませんでした。
リンク先のサイトはEntityFrameworkやZ.EntityFramework.Extensions.EFCore を使うよりもSqlBulkCopyを使った方が早かったですよ。という内容でした。
1対多のテーブルで多のデータをコピーや移行する時はループで回したりNew List<xxxxxx>をするよりSelect+Fromでコピーさせる
こちらはDB間でデータの移行したり、複製したりする時にパフォーマンスを改善する手段です。通常複数のデータを複製したりするときは下記のようにnewし直したり中のFK等のデータだけ変えたい時にループで回して設定したりするのが通常ですが、Select+Fromを使って使う側はコードも実際の処理時間も若干短縮させることができます。
下記ソースは実際の環境で打ったものではなく、説明のためにイメージで打っていますのであらかじめご了承ください。(構文ミス・スペルミスありそう)
修正前
IQueryable copymoto = dbcontext.entity().AsNoTracking; 1. var copysaki = new List<Entity>(copymoto); 2. List<Entity> copysaki = new List<Entity>(); foreach(var item in copymoto) { copy = new Entity() { id = guid(), Attribute = item.Attribute, 省略 }; copysaki.Add(copy); }
修正後
IQueryable copymoto = dbcontext.entity().AsNoTracking; var copysaki = copymoto.select( x => From(copymoto)); 省略 } // 別のメソッド private Entity From(Entitycopymoto) { id = guid(), Attribute = copymoto.Attribute, 省略 }
Parallel.For/ Parallel.Foreachを使い、ループ処理そのものを並列化する
こちらは例えばリード後の大量データをファイルに出力したりするときに使います。とはいえ、並列処理ということでソートをきちんとして出したい時にはもしかしたら使用できないかもしれません。こちらについては以下のリンクにとても詳しく書かれているので参照してみてください。
(C#)Parallel.For, Parallel.ForEach並列処理の挙動確認
このParallelを使用したループできるのはあくまで出力/表示時に有効でDB書き込みに使おうとするとエラーになってしまいましたので用途限定なのが残念です。