Deploy a Laravel application on AWS Elastic Beanstalk and scale it with Memcache

Want to learn how to create a Laravel application on Elastic Beanstalk that is ready to scale? We’ll explore how to set up your Elastic Beanstalk environment, hook it up to a database, deploy your application, and finally 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).
  • An AWS account. If you haven’t used AWS before, you can set up an account here.
  • The AWS CLI installed and configured on your computer.
  • PHP, Composer, git, and the EB CLI installed on your computer.

Create a Laravel application for Elastic Beanstalk

To start, we create a Laravel skeleton app like so:

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.

Initialize an Elastic Beanstalk application

Now we need to associate the Laravel skeleton with an Elastic Beanstalk app. Create and configure your EB app with the following steps:

  1. Create an .ebextensions folder and add a 01-environment.config file:

    Include the required environment variables and PHP config in .ebextensions/01-environment.config:

    You can create your own APP_KEY with php artisan key:generate --show.

  2. Create an Elastic Beanstalk repo:

    This will set up a new application called laravel-memcache. Then create an environment to run our application in:

    Notice that we’re adding a MySQL database to our EB environment. You’ll be prompted for a username and password for the database. You can set them to whatever you like.

    Be careful when choosing your password. AWS does not handle symbols very well (! $ @ etc.), and can cause some unexpected behavior. Stick to letters and numbers, and make sure it’s at least eight characters long.

    This will create a AWS Relational Database Service (RDS) instance that is associated with this application. When you terminate this application, the database instance will be destroyed as well. If you need a RDS instance that is independent of your Elastic Beanstalk application, create one via the AWS RDS interface.

    This configuration process will take about five to fifteen minutes. Go refill your coffee, stretch your legs, and come back later.

Deploy the Laravel skeleton to Elastic Beanstalk

Deploying our skeleton Laravel app can be done with two simple steps:

  1. Initialize a Git repository to commit the skeleton. Note that the Laravel skeleton already comes with a .gitignore file.

  2. Deploy the Laravel application on EB is easily done by running the deploy command:

    You can now open the application and see if it’s working:

We now have an EB environment with a running Laravel application. The application does not do much though, so let’s implement some task list functionality.

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 MySQL database

Since our EB environment already has a MySQL database initialized, we’ll need to add a way to connect to it through our app.

While you may want to use PostgreSQL, there is currently a bug in the eb cli that prevents you from creating a PostgreSQL instance in us-east-1 . We currently have an open ticket, and if anything changes, we’ll update this post.

To use our database, configure it like so in config/database.php:

The database config is based on the RDS_* environment variables which are set by Beanstalk automatically whenever a database instance is created.

Local setup

If you want to test your app locally (optional), we recommend using SQLite. To do so, make sure you have php-sqlite installed and configure the SQLite connection in config/database.php:

To use this connection locally, set DB_CONNECTION=sqlite in your app’s .env file.

Issue with MySQL <5.7

At the point of this writing, when you create an Elastic Beanstalk environment with a MySQL database as we did above, it will come with MySQL 5.6. Unfortunately, this version does not work with Laravel out of the box and the running the migrations will result in the following error:

SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too
long; max key length is 767 bytes (SQL: alter table `users` add unique
`users_email_unique`(`email`))

To get around this you can either update your MySQL instance or add the following to app/Providers/AppServiceProvider.php:

The database is now ready to use. Save the changes with:

Create the Task model

Now we have an empty database. To create and store tasks, we need to do three things:

  1. Create a migration that will create the tasks table:

    Tasks should have names, so let’s add name to the tasks table in the newly created database/migrations/<date>_crate_tasks_table.php file:

  2. Create a Task model to easily access the tasks table from our code:

    This creates an empty Task model in app/Task.php. Laravel automatically infers its fields from the migration.

  3. Apply the migration to your database:

    In order for EB to run the migrations upon deployment, we’ll have to include another config file telling it to do so.

    Inside .ebextensions/02-deploy.config include:

    If you set up SQLite locally, go ahead and create the database and run the migrations (optional):

Finally, commit your changes:

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:

Now we can push the changes to Elastic Beanstalk and see the result:

Test the application by adding a few tasks. We now have a functioning task list running on Elasitc Beanstalk. With this complete, we can learn how to improve its performance with Memcache.

If you get a 500 error when you open the application, check the logs. They’re located in the EB console in the side menu labeled Logs. Check your ENV variables to make sure they’re set correctly.

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 one for free from MemCachier. MemCachier provides a fast and flexible Memcache compatible with the protocol used by the popular memcached software. So head over to https://www.memcachier.com, sign up for an account, and create a free development cache.

There are three configuration variables you’ll need for your application to be able to connect to your cache: MEMCACHIER_SERVERS, MEMCACHIER_USERNAME, and MEMCACHIER_PASSWORD. You can find these on your cache’s analytics dashboard. You’ll need to add these variables to EB.

We can confirm that they’ve been set by running:

You should see your MemCachier env variables, as well as all the previous env variables we’ve set.

Configure your Memcache

To use Memcache locally you will need to install some dependencies:

  1. Install the php-memcached PECL extention via your OS package manager.
  2. Uncomment ;extension=memcached.so in /etc/php/conf.d/memcached.ini.
  3. Run php -m to make sure the memcached module is loaded.

On EB, this dependency is already installed and configured.

To set up Memcache in Laravel, we add the following dependency to composer.json:

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:

Now push the changes to EB 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:

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:

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:

Caching entire reponses

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.

Clean up

Once you’re done with this tutorial and don’t want to use it anymore, you can clean up your EB instance by using:

This will clean up all of the AWS resources.

Further reading & resources