Laravelのlazyとchunk

クエリの組み立て方ミスってたので全然比較できてないことに書いて5分後に気づきました!!!! all()->lazy() で取っちゃダメやん。query()->lazy()でとらないとダメ。なお、この記事は修正してないのでミスってるままです(sad)

翌日書いた記事はこれ

Laravelでクエリを構成する際に、lazy()とchunk()というメソッドを使うことができます。 この2つのメソッドの使い道ですが、結構な数のデータを扱う際に、メモリの消費を抑えてくれるらしいです。 公式ドキュメント

それで、どういうクエリを発行しているか気になったので、ちょっと覗きに行きました。

テストコードはこんな感じ。

class LazyTest extends TestCase
{
    use RefreshDatabase;
    public function test_lazy(): void
    {
        Product::factory(100)->create();
        DB::enableQueryLog();
        Product::all()->lazy()->each(function ($product) {
            $this->assertNotNull($product->name);
        });

        $queries = DB::getQueryLog();

        foreach ($queries as $query) {
            logger()->info([
                'sql' => $query['query'],
                'bindings' => $query['bindings'],
                'time' => $query['time'],
            ]);
        }
    }

    public function test_chunk(): void
    {

        Product::factory(100)->create();
        DB::enableQueryLog();
        Product::query()->chunk(10, function ($products) {
            $this->assertCount(10, $products);
        });

        $queries = DB::getQueryLog();

        foreach ($queries as $query) {
            logger()->info([
                'sql' => $query['query'],
                'bindings' => $query['bindings'],
                'time' => $query['time'],
            ]);
        }
    }

lazyはこんな感じ。

[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products`',
  'bindings' => 
  array (
  ),
  'time' => 0.27,
)  

chunkはこれ。

[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 0',
  'bindings' => 
  array (
  ),
  'time' => 0.2,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 10',
  'bindings' => 
  array (
  ),
  'time' => 0.14,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 20',
  'bindings' => 
  array (
  ),
  'time' => 0.13,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 30',
  'bindings' => 
  array (
  ),
  'time' => 0.13,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 40',
  'bindings' => 
  array (
  ),
  'time' => 0.14,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 50',
  'bindings' => 
  array (
  ),
  'time' => 0.13,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 60',
  'bindings' => 
  array (
  ),
  'time' => 0.12,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 70',
  'bindings' => 
  array (
  ),
  'time' => 0.13,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 80',
  'bindings' => 
  array (
  ),
  'time' => 0.14,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 90',
  'bindings' => 
  array (
  ),
  'time' => 0.12,
)  
[2023-07-23 13:20:38] testing.INFO: array (
  'sql' => 'select * from `products` order by `products`.`id` asc limit 10 offset 100',
  'bindings' => 
  array (
  ),
  'time' => 0.11,
)  

そもそも発行しているqueryが異なる感じ。 挙動として、chunkがすごくシンプルで limit と offset で数を絞ってDBを叩いて、取ってきた結果がcollectionになる。 そのcollectionを開発者側で操作する感じ。

lazyは1件のレコードの操作を開発者が指定して書く感じ。 ただ、SQLは一回発行して終わりなのがちょっと謎で、結局メモリに全データを乗っけないといけないのでは?という感想。 というか、それならall()で取ってきてforeachするのと大差ないじゃんという。

ただ、内部的にPHPのジェネレータという処理を使っているっぽくて、それが省メモリのキモらしいです。もちろん何もわかってません。

というわけで、こちらがジェネレータの説明書です。

読んだ感想ですが、なるほど... 確かに、xrangeのような使い方だと一度に大きな配列を返さなくていいのでメモリのエコになりますが、そもそも全データをメモリに乗っけてあるなら意味なくね?という感想。多分この考えが間違っているはず。

lazyの中身を見ると思い違いが発覚して理解できそうなので、後日debugします