Free eBook: Build Your First Node App

Caching in Laravel with Speed Comparisons

Chris Nwamba
👁️ 46,208 views
💬 comments

Caching is one aspect of web development I am guilty of overlooking a lot and I am sure a lot us are guilty of that too. I have come to realize how important it is and I will explain the importance with Scotch as a case study.

From experience and a little observation, Scotch schedules articles daily. Therefore, articles are not (for now) released within 24 hours of the last post. It follows that data on the landing page will remain the same for 24 hours. The main point here is, it is pointless to ask the database for articles within that 24 (or to be safe 22 - 23) hour range.

Cache to the rescue! In Laravel, we can cache the results for 22 hours and when a request is made, the controller responds with a cached value until the cache time expires.

Table of Contents

    We're going to look at the basic usage of the Laravel cache and then get into a quick demo app to see just how much faster caching can make our applications.

    Basic Usage of Cache in Laravel

    Laravel makes it easy for us to switch out how we want caching to be generated. It is extremely easy for us to switch out the drivers. Just check out config/cache.php to see the available drivers which are:

    • apc
    • array
    • database
    • file
    • memcached
    • redis

    You can then set the following line in your .env file to change the cache driver:

    CACHE_DRIVER=file

    You may go ahead and try these examples out without bothering about configuration, as it defaults to file.

    The Cache facade exposes a lot of static methods to create, update, get, delete, and check for existence of a cache content. Let us explore some of these methods before we build a demo app.

    Create/Update Cache Value

    We can add or update cache values with the put() method. The method accepts 3 necessary arguments:

    • a key
    • the value
    • the expiration time in minutes

    For example:

    Cache::put('key', 'value', 10);

    The key is a unique identifier for the cache and will be used to retrieve it when needed.

    Furthermore, we can use the remember() method to automate retrieving and updating a cache. The method first checks for the key and if it has been created, returns it. Otherwise, it will create a new key with the value returned from it's closure like so:

    Cache::remember('articles', 15, function() {
        return Article::all();
    });

    The value 15 is the number of minutes it will be cached. This way, we don't even have to do a check if a cache has expired. Laravel will do that for us and retrieve or regenerate the cache without us having to explicitly tell it to.

    Retrieve Cache Value

    The values of a cache can be retrieved using the get() method which just needs a key passed as argument to know which cache value to grab:

    Cache::get('key');

    Check for Existence

    Sometimes it is very important to check if a cache key exists before we can retrieve or update it. The has() method becomes handy:

    if (Cache::has('key')){
        Cache::get('key');
    } else {
        Cache::put('key', $values, 10);
    }

    Removing Cache Value

    Cache values can be removed with the forget() method and passing the key to it:

    Cache::forget('key');

    We can also retrieve a cache value and delete it immediately. I like referring to this one as one-time caching:

    $articles = Cache::pull('key');

    We can also clear the cache even before they expire from the console using:

    php artisan cache:clear

    Cache by Example

    This is going to be a really simple demo based on my research on the time taken to process requests with or without caches. To get straight to the point, I suggest you set up a Laravel instance on your own and follow along with the tutorial.

    Model and Migrations

    Create a model named Article using the command below:

    php artisan make:model Article -m

    The -m option will automatically create a migration for us so we need not run a "create migration" command. This single command will create an App/Article.php and a database/migrations/xxxx_xx_xx_xxxxxx_create_articles_table.php file.

    Update your new migration file to add two table fields:

    public function up() {
        Schema::create('articles', function (Blueprint $table) {
            $table->increments('id');
    
            // add the following
            $table->string("title");
            $table->string("content");
    
            $table->timestamps();
        });
    }

    Now we can migrate our database using the artisan command:

    php artisan migrate

    Seeding our Database

    Next up is to seed the articles table. In database/seeds/DatabaseSeeder.php, update the run() command with:

    public function run() {
        Model::unguard();
    
        // use the faker library to mock some data
        $faker = Faker::create();
    
        // create 30 articles
        foreach(range(1, 30) as $index) {
            Article::create([
                'title' => $faker->sentence(5),
                'content' => $faker->paragraph(6)
            ]);
        }
    
        Model::reguard();
    }

    The Faker library is included in Laravel to help with quickly generating fake data. We are using the PHP's range() method to generate 30 fake columns.

    Now we can seed our database with the artisan command:

    php artisan db:seed

    Creating an Article Controller

    Next up, we can create a controller that will process the requests and caching. It will be empty for now:

    php artisan make:controller ArticlesController

    ...then we add a route in the app/Http/routes.php which will point to the controller's index method:

    Route::group(['prefix' => 'api'], function() {
    
        Route::get('articles', 'ArticlesController@index');
    
    });

    Now that our database is all set up with sample data, we can finally get to testing.

    Responses Without Cache

    Let us see what our conventional controller action methods look like without caching and how long it takes to process the response. In the index() method, return a resource of articles:

    public function index() {
        $articles = Articles::all();
        return response()->json($articles);
    }

    You can now run the app and access the url (http://localhost/api/articles) from Postman or in browser as seen below.

    Take note of the time taken to complete this request on a local development server.

    Responses with Cache

    Let us now try to use caching and see if there will be any significant difference in the time taken to respond with data. Change the index() method to:

     public function index() {
        $articles = Cache::remember('articles', 22*60, function() {
            return Article::all();
        });
        return response()->json($articles);
    }

    Now we are caching the articles using the remember() method we discussed for 22 hours. Run again and observe the time taken. See my screenshot:

    Results & Recommendation

    From my standard development PC, the time taken to produce a response when using cache is less compared to when not as seen in the table:

    Without Cache

    Server Hits Time
    1st 4478ms
    2nd 4232ms
    3rd 2832ms
    4th 3428ms
    Avg 3742ms

    With Cache (File driver)

    Server Hits Time
    1st 4255ms
    2nd 3182ms
    3rd 2802ms
    4th 3626ms
    Avg 3466ms

    With Cache (Memcached driver)

    Server Hits Time
    1st 3626ms
    2nd 566ms
    3rd 1462ms
    4th 1978ms
    Avg 1908ms :)

    With Cache (Redis driver)

    It is required to install predis/predis via composer

    Server Hits Time
    1st 3549ms
    2nd 1612ms
    3rd 920ms
    4th 575ms
    Avg 1664ms :)

    Awesome enough right? Two things to note:

    1. The first hits take much more time even when using cache. This is because the cache is still empty during the first hit.
    2. Memcached and Redis are way faster compared to file. It is advised we use an external cache driver when our project is large.

    Conclusion

    The speed difference might not be so obvious with a file/database driver, but if we use external providers, we will see a lot better performance. It pays to invest in caching.

    Chris Nwamba

    92 posts

    JavaScript Preacher. Building the web with the JS community.