laravel / ideas

Issues board used for Laravel internals discussions.
938 stars 28 forks source link

Cache Eloquent relationships #1433

Open AliN11 opened 5 years ago

AliN11 commented 5 years ago

Hi. I searched the web and I think there is no way to cache an eloquent relationship. This would be useful for example when I have posts and want to cache only their comments.

// App\Models\Post.php

public function comments()
{
    return Cache::remember('comments', 180, function(){
        return $this->belongsTo('App\Models\Comment');
    })
}
// This produces: You cannot serialize or unserialize PDO instances
$post->comments;
mfn commented 5 years ago

This would be useful for example when I have posts and want to cache only their comments.

I don't understand your question. For each model, relations are only loaded the first time they're "requested". This most commonly happens via ->relation or using ->with() on the query. So in a sense, they are cached.

What your example caches is the "definition of the relation", something different.

Maybe we need more information, but currently it's not clear what problem you have/try to solve 🤷‍♀️

AliN11 commented 5 years ago

@mfn Thanks for reviewing. It's simple. For example we have posts and comments in our website. A post has 40 comments and no new comment has been posted for a while. So it is better to cache these comments instead of fetching them from DB on every page load.

What you mentioned, works only for one page request. I mean if I refresh the page, nothing caches.

drbyte commented 5 years ago

@AliN11 perhaps the laravel-model-caching package by @mikebronner will suit your needs.

AliN11 commented 5 years ago

@drbyte It's cool. But it doesn't cache a specific relationship.

mfn commented 5 years ago

I'm still not clear on your goal but I'm trying to summarize it: you want to cache (all/specific?) relations on a model across requests?

fico7489 commented 5 years ago

I understand what is an @AliN11 goal. for example if you have 1 post and that post have 20 comments, with this code:

$post = Post::with('comments')->find(1);

two DB queries are excecuted :

select * from posts ...
//and
select * from comments

for same code from above, he want only one query

select * from posts ...

and get comments from cache...

AliN11 commented 5 years ago

@mfn, Exactly what @fico7489 said.

// Models\User.php

public static function all()
{
   $minsPerDay = 1440;

    return $users = Cache::remember('users', $minsPerDay, function() {
        return User::all();
    });
}

You know above code caches users for 24 hours and keeps output in the filesystem, Redis, etc. So it means that we have only one query to DB per day, no matter how many visits our page/application has.

But there is no way to apply above code in relationships. So you can't cache queries within relationships. So think of that what happens if our page has 300K views per day and you call $post->comments on every request.

Patryk27 commented 5 years ago

So think of that what happens if our page has 300K views per day and you call $post->comments on every request.

Probably nothing scary, since simple SELECT queries utilizing indexes are pretty quick and should be automatically cached by the database engine anyway.

One way or another: instead of sending 300K queries to a multi-threaded MySQL / MariaDB / Postgres, you're now sending 300K queries to a single-threaded Redis or (even worse!) the filesystem itself, so nothing really changed.

AliN11 commented 5 years ago

@Patryk27 You are right but I don't mean that. It was just an example. Examples are always simple :D Sometimes before performing cache, data are processed and mixed with some other data and queries. That's why caching is useful. It prevents duplicate calculation and processing.

Patryk27 commented 5 years ago

Well then - show a more complex example that could be solved inside the framework :-)

As for now, I think that it seems more like a domain-related thing that should be handled entirely by the application, not framework.

AliN11 commented 5 years ago

@Patryk27 I know there are some tricky ways, however $post->comments is impossible to cache.

mfn commented 5 years ago

however $post->comments is impossible to cache

Did you try with a custom getter or are they overridden in such a case?

AliN11 commented 5 years ago

@mfn I tried. If we override the __get method which exists in Illuminate\Database\Eloquent\Model, We can't use any other relationships like this: $post->likes anymore.

If we use __get() like bellow:

public function comments()
{
    return $this->belongsTo('App\Models\Comment');
}

public function __get($name)
{
    return $name;
}

And if we try $post->comments, the getter executes and comments() method is ignored. It means that all relationships are ignored by adding __get()

mfn commented 5 years ago

Sorry I meant getCommentsAttribute, did you try that?

AliN11 commented 5 years ago

@mfn No I'll try that.

luke83 commented 3 years ago

Don't know if still relevant, anyway I think this could help in such scenario:

https://stackoverflow.com/a/56263511

al-one commented 3 years ago

https://github.com/laravel/framework/pull/35411

https://github.com/al-one/eloquent-super-relations