Skip to content

[12.x] Use big integers for database cache expiration column#59243

Merged
taylorotwell merged 1 commit intolaravel:12.xfrom
tanerkay:database-cache-expiration-year-2038
Mar 17, 2026
Merged

[12.x] Use big integers for database cache expiration column#59243
taylorotwell merged 1 commit intolaravel:12.xfrom
tanerkay:database-cache-expiration-year-2038

Conversation

@tanerkay
Copy link
Contributor

Problem

The database cache driver treats "forever" cache items as having an expiry of ~10 years from the current time. From sometime in mid-late Jan 2028, any cache addition without an explicitly given shorter TTL will start causing "out of range" exceptions on database platforms that use 4 byte integer columns (most, with a notable exception of SQLite).

This means that applications using a cache table as it is currently with the database cache driver will face this issue relatively soon, and will need to run a database migration to address the issue. By changing the cache table migration stub, we can mitigate that hassle for future applications.

To reproduce:

Carbon::setTestNow('2028-02-01');

config()->set('database.default', 'mysql'); // or other database connection

cache()->driver('database')->forever('foo', 'bar');
// Illuminate\Database\QueryException: SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'expiration' at row 1

Solution:

Use big integers (four bytes of extra storage per row). Simplest, most backwards-compatible way I could think of implementing this.

Note: doesn't affect existing cache tables, only new ones created from the stub, e.g. using php artisan make:cache-table.

Considerations:

Extra storage size. Somewhat negligible these days, and considering that every cache record is stored with a key that has a prefix of several characters, e.g. app_name_cache:, the extra impact of 4 bytes seems small.

Performance: I'm not invested in this enough to run benchmarks. Probably negligible?

Alternatives considered:

  • use a timestamp column (MySQL timestamp columns are signed 4 byte values, so same issue. SQLite has no timestamp column type, so also potentially problematic)
  • use an unsigned integer (no extra storage), but this has limited support, e.g. PostgreSQL does not support unsigned integers.
  • use a datetime column (one byte of extra storage per row), but this has potential timezone/DST handling issues, particularly for applications with an app.timezone that is not set to UTC.

Notes:

A test could be added to DatabaseCacheStoreTest to ensure the expiration value is stored correctly and not clipped, e.g.:

    public function testYear2038Timestamps()
    {
        Carbon::setTestNow('2028-02-01');

        $store = $this->getStore();

        $store->forever('foo', 'bar');

        $this->assertDatabaseHas($this->getCacheTableName(), [
            'key' => $this->withCachePrefix('foo'),
            'value' => serialize('bar'),
            'expiration' => Carbon::now()->addSeconds(315360000)->timestamp,
        ]);
    }

But this requires a change in orchestra/testbench: https://github.com/orchestral/testbench-core/blob/11.x/laravel/migrations/0001_01_01_000001_testbench_create_cache_table.php

@tanerkay
Copy link
Contributor Author

Potential follow-up: increase the threshold for "forever" cache items for the database cache driver to more than ~10 years.

@taylorotwell taylorotwell merged commit 6eaf931 into laravel:12.x Mar 17, 2026
71 of 72 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants