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.
In this guide we will create a task list application based on the Laravel 5.2 tutorial and scale it with Memcache. The sample app in this guide can be found here.
Prerequisites
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/<date>_create_tasks_table.php
file:
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 thememcached
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:
SESSION_DRIVER=memcached
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
- MemCachier Documentation
- Manifold Laravel integration
- Laravel Caching Documentation
- Laravel Documentation
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.