framework icon indicating copy to clipboard operation
framework copied to clipboard

Only merge cached casts for accessed attribute

Open ug-christoph opened this issue 1 month ago • 7 comments

This is a PoC for the issues discussed in https://github.com/laravel/framework/discussions/31778

One major performance hog that I noticed in my laravel jobs is the fact that all model attributes that have a cast and were already accessed are serialized everytime any attribute of the model is accessed. This happens in HasAttributes.php in the method mergeAttributesFromClassCasts.

Here is an example:

$user = User::first();
$someCastedAttribute = $user->some_casted_attribute;
$id = $user->id; // <-- this line triggers mergeAttributesFromClassCasts which causes a serialization of some_casted_attribute

This behavior is very expensive if the casted attributes are large objects. (e.g. an instantiated class from a large json column).

Let's assume $user->some_casted_attribute holds a class with 100kB of loaded data

for($i = 0; $i < 1000; $i++) {
    $id = $user->id;
}

This loop will cause 1000 calls to json_encode($this->some_casted_attribute) which effectively encodes a total of 100MB of data, even though some_casted_attribute is never used in the loop.

My PR includes a PoC that makes sure that only the attribute that is really accessed via getAttribute is serialized. This is optional behavior that I added to mergeAttributesFromClassCasts and it will behave as it used to for all other places in the codebase that access it.

The linked github discussion includes further suggestions how to reduce the calls for places like isDirty checks, but I decided to stick to the highest ROI change with the lowest chance of negative side effects in this PR.

Tests are passing and I cannot think of a way how this would break something, but I am happy to get confirmation that the approach is sound and that I am not overlooking some edge case or reason why we would actually want to serialize all casted attributes even when we just access id.

My benchmarks showed a huge improvement for my workloads. Before the change my code spent over 80% of the total execution time in mergeAttributesFromClassCasts. With the change this reduced <20% for most cases (still high, but definitely an improvement, that will save significant server costs). Disclaimer: My workload actually contains large json columns (10-100kB).

The screenshot lists some of my jobs which have a high execution time % spent in mergeAttributesFromClassCasts with the time spent before and after this proposed change.

image

ug-christoph avatar Nov 01 '25 09:11 ug-christoph