How to scale a Laravel application with Memcache
How to scale a Laravel application with Memcache
Want to learn how to create a Laravel application that is ready to scale? In this tutorial we’ll explore how to create your Laravel application, hook it up to a Manifold project, and finally learn how to use Memcache to speed it up.
Memcache is a technology that improves the performance and scalability of web apps and mobile app backends. You should consider using Memcache when your pages are loading too slowly or your app is having scalability issues. Even for small sites, Memcache can make page loads snappy and help future-proof your app.
Before you complete the steps in this guide, make sure you have all of the following:
Familiarity with PHP (and ideally some Laravel).
A Manifold account. Sign up for free here.
The Manifold CLI installed and logged in.
PHP and Composer installed on your computer.
Create a Laravel application
To start, we create a Laravel skeleton app like so:
$ composer create-project laravel/laravel --prefer-dist laravel_memcache Installing laravel/laravel (v5.7.15) - Installing laravel/laravel (v5.7.15): Loading from cache Created project in laravel_memcache ... $ cd laravel_memcache
If you want, you can run the app with php artisan serve and visit it at http://127.0.0.1:8000/. You will see the Laravel skeleton page.
Set up a Manifold project for our application
To manage the resources we will use, create a new Manifold project:
$ manifold projects create laravel-memcache $ manifold init --project laravel-memcache
Now create a read-credentials API token to read the Manifold credentials.
$ manifold tokens create
Add MANIFOLD_PROJECT=laravel-memcache and MANIFOLD_API_TOKEN=<YOUR_TOKEN> to your .env file.
To use the Manifold resources in Laravel, we need to install and publish Manifold’s service provider:
$ composer require manifoldco/manifold-laravel $ php artisan vendor:publish
Now you can access all configuration variables stored within Manifold with config("RESOURCE_NAME.CONFIG_VARIABLE").
Add task list functionality
Let’s add a task list to the app that enables users to view, add, and delete tasks. To accomplish this, we need to:
Set up the database
Create a Task model
Create the view and controller logic
Set up the database
For this simple example we will use an SQLite database. In a production application you should use a more serious database than SQLite. You can easily get a hosted database on Manifold, such as the JawDB database service.
To use SQLite, make sure you have php-sqlite installed and configure the SQLite connection in config/database.php:
To use this connection, set DB_CONNECTION=sqlite in your app's .env file.
Create the Task model
Now we have an empty database. To create and store tasks, we need to do three things:
Step 1) Create a migration that will create the tasks table:
$ php artisan make:migration create_tasks_table --create=tasks
Tasks should have names, so let’s add name to the tasks table in the newly created database/migrations/
Step 2) Create a Task model to easily access the tasks table from our code:
$ php artisan make:model Task
This creates an empty Task model in app/Task.php. Laravel automatically infers its fields from the migration.
Step 3) Create the database and apply the migration:
$ touch database/database.sqlite $ php artisan migrate --force
Add a view for the task list
To view the tasks stored in the database, we create a view that lists all tasks. We start with a boilerplate layout:
We can now create the task list view as a child view of the above layout:
Ignore the TODOs for now (we'll fill them out later). To be able to access this view, connect it to the top route in routes/web.php:
If you have a local setup, you can now start a web server with php artisan serve and visit the view at localhost:8000. However, there isn't much to look at yet because the task list is empty.
Enable task creation
In order for the task list to be more useful, users need to be able to add tasks. Let’s create a card for that:
Because the task name is provided by the user, we need to make sure the input is valid. In this case, the name must exist, and it must not exceed 255 characters. If the input fails to validate according to these rules, we want to display the following error view:
Let’s add these new views to routes/web.php:
Starting a local web server with php artisan serve and visiting localhost:8000 is now a bit more interesting because you can add tasks.
Enable task deletion
To complete our task list, we also need to be able to remove completed tasks. To delete a task, we add a Delete button to each item in the task list:
Then we wire this functionality to the appropriate route in routes/web.php:
Run php artisan serve and test the application by adding a few tasks. We now have a functioning task list. With this complete, we can learn how to improve its performance with Memcache.
Add caching to Laravel
Memcache is an in-memory, distributed cache. Its primary API consists of two operations: SET(key, value) and GET(key). Memcache is like a hashmap (or dictionary) that is spread across multiple servers, where operations are still performed in constant time.
The most common use for Memcache is to cache expensive database queries and HTML renders so that these expensive operations don’t need to happen over and over again.
Set up Memcache
To use Memcache in Laravel, you first need to provision an actual Memcache cache. You can easily get a free MemCachier cache from Manifold which provides you a fast and flexible Memcache compatible with the memcached protocol.
$ manifold create --product memcachier-cache --plan dev --region us-east-1 memcachier
This creates a MemCachier cache with the name memcachier. There are three configuration variables now present in your Manifold config: MEMCACHIER_SERVERS, MEMCACHIER_USERNAME, and MEMCACHIER_PASSWORD. You can also find these on your cache's analytics dashboard which you can access via manifold sso memcachier.
Configure your Memcache
To use Memcache locally you will need to install some dependencies:
Install the php-memcached PECL extention via your OS package manager.
Uncomment ;extension=memcached.so in /etc/php/conf.d/memcached.ini.
Run php -m to make sure the memcached module is loaded.
To set up Memcache in Laravel, we add the following dependency to composer.json:
$ composer require ext-memcached
We then configure the cache in config/cache.php:
This configures Laravel’s caching engine with MemCachier, which allows you to use your Memcache in a few different ways:
Directly access the cache via get, set, delete, and so on
Cache results of functions with the rememberForever function
Use Memcache for session storage
Cache rendered partials
Cache entire responses
Cache expensive database queries
Memcache is often used to cache the results of expensive database queries. Of course, our simple task list does not have any expensive queries, but let’s assume for this tutorial that fetching all of the tasks from the database is a slow operation.
The rememberForever function makes it easy to add caching to Laravel. You provide two arguments to it:
A cache key
A function that queries your database and returns results
The rememberForever function looks up the key in your cache. If the key is present, its corresponding value is returned. Otherwise, the database function you provided is called. Whatever that function returns is then stored in the cache with the corresponding key for future lookups.
This means that the first time you call rememberForever, the expensive database function is called, but every successive call to rememberForever obtains the value from the cache.
Use the rememberForever function to easily add caching to the task view controller in routes/web.php:
As you might have noticed, we now have a problem if we add or remove a task. Because rememberForever fetches the task list from the cache, any changes to the database won't be reflected in the task list. For this reason, whenever we change the tasks in the database, we need to invalidate the cache:
View Memcache statistics
To help demystify Memcache caching operations, we can visualize what’s going on under the hood.
First, we obtain stats every time the task list is requested in routes/web.php:
Then, we add a card for the stats at the bottom of the task view:
Run php artisan serve and see the how the stats change when you play with the task list. You can see that the first time you access the page, the Get misses increase by one. This is because the first time rememberForever is called, the task list is not in the cache. The Set commands also increase because the task list is saved to the cache. If you refresh the page, the misses stay the same, but the Get hits increase because the task list is served from the cache.
When you add a new task or delete a task, your misses will increase again because the cache was invalidated.
You can also see the same stats and more on the analytics dashboard of your cache from within your MemCachier account.
Caching rendered partials
With the help of laravel-partialcache, you can cache rendered partials in Laravel. This is similar to fragment caching in Ruby on Rails or snippet caching in Flask. If you have complex partials in your application, it’s a good idea to cache them because rendering HTML can be a CPU-intensive task.
Do not cache partials that include forms with CSRF tokens.
Our example does not include any complex partials, but for the sake of this tutorial, let’s assume that rendering the task name in the task list takes a lot of CPU cycles and slows down our page.
First, we need to add the laravel-partialcache package to our app:
$ composer require spatie/laravel-partialcache
Second, let’s factor out the task name into a partial:
Now we can import and cache this partial in our task view:
This caches each task name partial with the ID as its key. Note that in this example, we never have to invalidate a cached partial because the name of a task can never change. However, if you add the functionality to change the name of a task, you can easily invalidate the cached partial with PartialCache::forget('task.name', $task->id);.
Let’s see the effect of caching the task name partials in our application by running php artisan serve. You should now see an additional Get hit for each task in your list.
Using Memcache for session storage
Memcache works well for storing information for short-lived sessions that time out. However, because Memcache is a cache and therefore not persistent, long-lived sessions are better suited to permanent storage options, such as your database.
Changing the session store from a file (default) to memcached can be done easily by setting the SESSION_DRIVER config var in your .env file:
Caching entire responses
In Laravel, it’s also easy to cache the entire rendered HTML response by using laravel-responsecache. This is similar to view caching in Ruby on Rails. This package is easy to use and has good documentation in its README. However, we cannot use it in our example because our task list contains forms with CSRF tokens. To use this package with Memcache, you have to set the config var RESPONSE_CACHE_DRIVER to memcached.
Further reading & resources
Special thanks to Sascha Trifunovic and the rest of the MemCachier team for submitting this post for us to publish on their behalf as part of Manifold’s 12 Days of Services.