11
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Symfony2, Doctrine, MySQL での大量データ処理時にメモリ量を一定に保つTips

Last updated at Posted at 2016-03-05

目的

  • バッチ処理などで大量のデータを処理する場合、データが増えてもメモリ使用量が一定になるようにしておきたい
    • データの増加に伴ってメモリ使用量が増えると、そのうち処理できなくなるので

Tips

1. Doctrine2のIterating resultsを使用する

  • Batch Processing - Doctrine Object Relational Mapper (ORM)
  • これによって、ハイドレーション(DBの結果をオブジェクトなどにマッピングする処理)時のメモリ使用量を抑えることができる
    • 取得してきたすべての結果セット分のオブジェクト生成を行わず、1つずつの生成になる
  • 定期的にclear(もしくはdetach)メソッドを呼ぶのも忘れずに
    • こちらはDoctrineが管理しているオブジェクトの情報を解放する

2. DBからは一度に全件を取得せずに、ある程度の塊として小分けで取得する

  • 1. の対応だけでは、DBの結果セットはすべて呼び出し側のメモリ(※1)にのってしまう

  • よってデータを小分けに取得する

  • ※1 PHP: バッファクエリと非バッファクエリ - Manual こちらの設定にもよる

    • libmysqlclient を使用している場合は、変数に入れるまで、phpから見たメモリ使用量に加算されない(memory_get_usage()にも加算されないし、memory_limitにも引っかからない)が、実際そのプロセスのメモリ使用量は増えるので注意すること

2-1. setMaxResults, setFirstResult を使う

  • いわゆる LIMIT, OFFSET を使う
  • しかし、大量のデータだと後半のデータに行けば行くほど取得するのに時間がかかる問題もある

2-2. id BETWEEN を使う

  • WHERE id BETWEEN :start AND :end で取得するようにすれば、インデックスをうまく使えるので、後半の取得になろうと一定のスピードで取得できる
  • しかし、idに歯抜けがあると、実際にはデータがない無駄なループが回ってしまう可能性がある
  • どちらを使うかはケースバイケース?

3. Logger に気をつける

  • DoctrineBundle のデフォルトでは kernel.debug = true だと、SQL呼び出しの度にそのクエリを記録するのでメモリ使用量が線形に増加してしまう、 よってコマンドの場合 --no-debug で実行すること
  • ログの記録を常にやめたいのであれば http://stackoverflow.com/a/10913115 にある方法で
    SQLLoggernull を入れておく
  • その他、HTTP通信するライブラリなどで、通信の詳細をLoggerに記録する場合もあるので、そういうところも必要に応じて設定を変更する

[2018/10/29追記] ライブラリにしました

下記の2点を自動で行うジェネレータのライブラリを作りました。
https://packagist.org/packages/kalibora/chunk-generator

  • Doctrine2のIterating resultsを使用する
  • id BETWEEN を使って、DBからは一度に全件を取得せずに、ある程度の塊として小分けで取得する
use Kalibora\ChunkGenerator\ChunkGeneratorBuilder;

$fooRepository = $manager->getRepository(Foo::class);
$qb = $fooRepository->createQueryBuilder('f');
$gen = ChunkGeneratorBuilder::fromDoctrineQueryBuilder($qb)->setChunkSize(200)->build();

// Iterate all foo entity. Fetch 200 records at a time and clear it.
foreach ($gen() as $foo) {
    echo $foo->getVar(), PHP_EOL;
}

こういう感じで使えます。

11
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?