Careful when using Laravel's LazyCollection

ยท

3 min read

Hey folks,

Just some research that I did in the past, to compare the normal Collection and LazyCollection of Laravel when querying for the data from Database.

Some useful notes for you if you haven't researched for it.

Let's go.

Quick introductions

  • Normal Collection: when querying for a set of records, it will immediately query and hydrate the models.

    • ->get()

    • Would get all records if you don't add the ->limit()

  • Lazy Collection: won't query until you actually invoke any methods and get the data by chunk

    • ->lazy()

    • ->lazyById()

    • ->lazyByIdDesc()

    • ->cursor() : this one is different, it would get all the data but won't hydrate the models at once and also can't be eager-loaded

PROs of Lazy indeed, save memory. But there will be consequences/trade-offs, are you ready?

Deep-dive into LazyCollection

Example Query

$users = User::select(['id', 'name'])
    ->orderBy('id')
    ->lazy();

Every Collection interaction will result in a Query to DB

Yes. Every time you invoke any method, it would query to the DB:

  • ->first() => will do SELECT id,name FROM users ORDER BY id LIMIT 1

  • ->last() => will do a query

  • ->isEmpty() => will do a query

  • ->isNotEmpty() => will do a query

  • ...

๐Ÿ˜ฑ๐Ÿ˜ฑ๐Ÿ˜ฑ

Unlike the normal Collection which data would be loaded at once in the memory, then you handle things on the memory.

You don't want to ram the DB on the HTTP layer, do you? Not to mention, too many queries will slow down your endpoints.

Avoid using count method

Using count would result in N queries. For example, if you do the count for 15k records with lazy(1000), it would do:

  • SELECT id,name FROM users ORDER BY id LIMIT 1000 OFFSET 0

  • SELECT id,name FROM users ORDER BY id LIMIT 1000 OFFSET 1000

  • ...

  • SELECT id,name FROM users ORDER BY id LIMIT 1000 OFFSET 16000

And then it will do the sum => cost you 16 queries to the database. Each query can take up to 50ms (depending on your DB's specs)

Horrible ๐Ÿ˜ฑ, IKR?

Imagine using this on HTTP Layer, it would take a big amount of time and slow down the endpoint.

Conclusion

Well, I can put it like this:

  • If you're going to get a normal amount of records (eg 10, 20, 100 even 500), normal get would serve you right and there won't be many issues.

    • Also only get the needed columns (->get(['id', 'name', ...])) too.

    • Collection over Query Builder as well.

  • If you're going to handle a gigantic amount of records (eg 10k, 20k,...), lazy would be the right choice, in order to handle things gently and save memory in the long run.

    • You won't probably handle 10k records on the HTTP layer, will you? ๐Ÿคฃ

Or TL;DR:

  • Normal Collection: on HTTP with not-so-many records (up to 1k)

  • Lazy Collection: on Queue/Console with the big amount (1k+)

Use it wisely, alright? Don't put everything in LazyCollection , use the right tool for the right job.

Cheers and happy speedup!

ย