業務アプリが遅くなる原因は、 UI・DB・非同期・キャッシュ のどこかに必ず潜んでいます。 「どこから手をつければいいか分からない」「とりあえずPCを増強している」 そんな状態から抜け出すための実務的な高速化ガイドです。
・UI(WPF)の高速化テクニック
・SQLiteのパフォーマンスチューニング
・非同期処理(async/await)の正しい使い方
・キャッシュ戦略(メモリ・ローカルDB・API)
・ボトルネックの見つけ方
・「体感速度」を上げる設計のコツ
1. 高速化の基本方針:「どこが遅いか」を切り分ける
まずは、闇雲に最適化するのではなく、 「UI」「DB」「ネットワーク」「ロジック」 のどこが遅いかを切り分けます。
■ ざっくり切り分けのコツ
- 画面表示が遅い → UI or データ取得
- 検索ボタン押下後に固まる → DB or API or 同期処理
- スクロールがカクつく → UIバインディング or DataGrid
- 初回起動だけ遅い → キャッシュ or 初期ロード
このあと、UI → DB → 非同期 → キャッシュ の順で 具体的な高速化テクニックを見ていきます。
2. UI高速化(WPF / MVVM)
UIが重い原因の多くは、 「表示しすぎ」「バインディングしすぎ」「同期処理しすぎ」 です。
■ 2-1. DataGridに全件バインドしない
100万件をそのままDataGridにバインドすると、ほぼ確実に固まります。
- ページング(1ページ100〜500件)
- 仮想化(Virtualization)をON
<DataGrid
EnableRowVirtualization="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingPanel.VirtualizationMode="Recycling"
/>
■ 2-2. 重い処理をUIスレッドで実行しない
DBアクセス・ファイルI/O・API呼び出しは 必ずasync/awaitでバックグラウンド実行します。
public async Task LoadAsync()
{
IsBusy = true;
var items = await _service.GetListAsync(); // 非同期
Items.Clear();
foreach (var x in items) Items.Add(x);
IsBusy = false;
}
■ 2-3. バインディングの数を減らす
- 頻繁に変わらない値はコードビハインドで設定
- 巨大なツリービュー・リストは必要な範囲だけ展開
「全部バインディング」はきれいですが、パフォーマンス的には重くなりがちです。
3. DB高速化(SQLiteチューニング)
SQLiteの高速化は、 インデックス・クエリ・トランザクション・WAL が鍵です。
■ 3-1. インデックス設計(最重要)
WHERE句・JOIN句に使う列には必ずインデックスを貼ります。
CREATE INDEX idx_orders_user_date ON Orders(UserId, OrderDate);
EXPLAIN QUERY PLAN でインデックスが使われているか確認します。
■ 3-2. 全件取得禁止 → ページング必須
SELECT * FROM Logs
ORDER BY Id
LIMIT @limit OFFSET @offset;
画面表示は「必要な分だけ取る」が鉄則です。
■ 3-3. WALモードで読み書きを高速化
PRAGMA journal_mode = WAL;
- 読み込みと書き込みの競合が減る
- ロック発生率が下がる
■ 3-4. バルクINSERTで大量登録を高速化
using var tran = con.BeginTransaction();
var sql = "INSERT INTO Logs (Message, CreatedAt) VALUES (@msg, @dt)";
foreach (var item in items)
{
con.Execute(sql, new { msg = item.Message, dt = item.CreatedAt }, tran);
}
tran.Commit();
1件ずつトランザクションを切るより桁違いに速くなります。
4. 非同期処理(async/await)の正しい使い方
高速化=「処理を速くする」だけでなく、 「待ち時間をUIに感じさせない」ことも重要です。
■ 4-1. I/Oは必ず非同期
- DBアクセス
- API呼び出し
- ファイル読み書き
同期版メソッド(.Result / .Wait())はUIスレッドでは使わない。
■ 4-2. ローディング表示で体感速度UP
IsBusy = true;
try
{
await LoadAsync();
}
finally
{
IsBusy = false;
}
「待っていることが分かる」だけで、ユーザーのストレスは大きく減ります。
■ 4-3. 並列化できる処理はまとめて待つ
var task1 = _service.LoadMasterAsync();
var task2 = _service.LoadSettingsAsync();
await Task.WhenAll(task1, task2);
順番に待つより、まとめて待つ方が体感速度は大きく向上します。
5. キャッシュ戦略(メモリ / ローカルDB / API)
キャッシュは「何を」「どこに」「どれくらいの期間」持つかが重要です。
■ 5-1. メモリキャッシュ(アプリ内)
- マスターデータ(都道府県・商品区分など)
- 設定値
- 最近使ったデータ
private readonly Dictionary<string, Customer> _cache = new();
public async Task<Customer> GetCustomerAsync(string id)
{
if (_cache.TryGetValue(id, out var cached))
return cached;
var customer = await _repo.FindByIdAsync(id);
_cache[id] = customer;
return customer;
}
■ 5-2. ローカルDBキャッシュ(SQLite)
API結果をSQLiteにキャッシュしておくと、 オフラインでも動く & API負荷も減る構成になります。
- 初回:API → SQLite保存 → UI表示
- 2回目以降:SQLite → UI表示(必要に応じてAPI更新)
■ 5-3. 有効期限付きキャッシュ
- マスターデータ:1日
- ダッシュボード:数分〜数十分
- リアルタイム性が不要なものは積極的にキャッシュ
「常に最新である必要があるか?」を一度疑ってみると、キャッシュの余地が見えてきます。
6. ボトルネックの見つけ方と優先順位
高速化は「一番遅いところ」からやるのが鉄則です。
■ 6-1. 体感ベースでのチェック
- どの画面が一番遅いか
- どの操作でユーザーが待たされているか
- どの処理が「ぐるぐる」しているか
■ 6-2. ログで計測する
var sw = Stopwatch.StartNew();
await _service.DoSomethingAsync();
_logger.LogInformation("DoSomething: {Elapsed} ms", sw.ElapsedMilliseconds);
「なんとなく遅い」から「どこが何msかかっているか」へ。
■ 6-3. 優先順位の付け方
- 利用頻度が高い処理
- 待ち時間が長い処理
- ビジネス的に重要な処理
全部を最適化する必要はなく、上位20%を潰すだけで体感は大きく変わります。
7. 業務アプリ向け高速化ベストプラクティス
- UI:全件表示禁止、仮想化+ページング、重い処理は非同期
- DB:インデックス設計、ページング、WAL、バルクINSERT
- 非同期:I/Oは必ずasync/await、ローディング表示、並列化
- キャッシュ:マスタ・設定・API結果をメモリ/SQLiteにキャッシュ
- ボトルネック:ログで計測し、頻度×時間×重要度で優先順位をつける
まとめ:高速化は“テクニックの寄せ集め”ではなく“設計”
- UI・DB・非同期・キャッシュをセットで考えると、無理のない高速化ができる
- 「全部最新」「全部表示」をやめるだけで、体感速度は大きく変わる
- 計測 → 仮説 → 改善 → 再計測 のサイクルを回すことが、本当の高速化につながる
「とりあえず速くしたい」 から一歩進んで、 「どこが遅いかを理解して、狙って速くする」 という発想に切り替えると、 業務アプリの品質は一段上がります。 この記事をベースに、あなたのプロジェクトに合った高速化戦略を組み立ててみてください。