A Laravel package to manage dynamic servers

https://freek.dev/og-images/38c00d66ffaaa8eb3dd0f18d49a128c9/2344.png

I’m proud to announce that our team has released a new package called laravel-dynamic-servers.

This package can help start and stop servers when you need them. The prime use case is to spin up extra working servers to help you process the workload on queues.

In this blog post, I’d like to introduce the package to you.

Why we’ve created this package

We are currently beta testing the hosted version of Mailcoach: an easy-to-use, affordable, platform to send email campaigns, and we also support drip campaigns if that’s what you need.

One of the important aspects of Mailcoach is privacy for those who need it. All open- and click tracking is optional. But we go farther from that. For GDPR-sensitive organizations, such as the government, our legal experts suggested that we would only use infrastructure located on EU territory and owned by EU-based companies.

I feel that if you ask 100 legal experts for advice on this, you’ll get 100 different suggestions. It might have been okay to stay on AWS, but we wanted to be on the safe side and avoid any American-owned companies.

The first version of Mailcoach ran on AWS Lambda via Laravel Vapor. We migrated it to UpCloud servers, a European-located and owned hosting company. Those servers are provisioned via Laravel Forge.

The significant benefit running servers on AWS infrastructure gave us was the automatic scaling. We need autoscaling when people simultaneously start sending campaigns to large email lists. During our beta, this scenario already played out: our service had to send out hundreds of thousands of emails (via the queue) in a short period.

UpCloud is not a serverless platform but uses regular servers. A traditional solution to autoscale servers and orchestrating server stuff would be to use Kubernetes. That piece of software is also very powerful but also hard to learn.

That’s why we invested time in creating a package that allows us to spin up extra servers to handle queued jobs whenever there is extra workload and to destroy them whenever the work is done.

Using dynamic servers

You can think of laravel-dynamic-servers as a sort of PHP-based version of Kubernetes that has 5% of its features but covers that 80% use case. For most PHP and Laravel developers, this package will also be easier to learn and use.

The package is driver based. It ships with support for UpCloud (because we needed that ourselves), but the community already created a DigitalOcean driver, and creating a driver of your own is easy.

Typically, on your hosting provider, you would prepare a server snapshot that will be used as a template when starting new servers.

After the package is installed and configured, you can use PHP to start and stop servers.

Here’s the most straightforward way to start a server via PHP code:

use Spatie\DynamicServers\Facades\DynamicServers;

DynamicServers::increase();

To start a server, the package will start a queued job that makes an API call to your server provider to spin up a server. It will also dispatch subsequent jobs to monitor the entire starting process of a server.

Stopping one server is equally simple:

DynamicServers::decrease();

In most cases, you would use these methods directly, though. The package also offers a method called ensure. You can pass it the number of servers you want to have available in total.

DynamicServers::ensure(5);

If fewer servers are currently active than the number given, more servers will spin up. The package will destroy a few if there are more servers than that number.

Usually, you would have code that calculates the number of servers you need and pass that number to ensure.

Here’s a simplified version of the calculation logic we used at Mailcoach. We use Horizon’s WaitTimeCalulator class to get the waiting time of the queue.

use Laravel\Horizon\WaitTimeCalculator;
use Spatie\DynamicServers\Facades\DynamicServers;

$waitTimesOfAllQueues = (WaitTimeCalculator::class)->calculate();

$maxWaitTime = max($waitTimesOfAllQueues);

// 1 server for every 5 minutes of wait time
$amountOfServersNeeded = floor($waitTimesOfAllQueues / 60 / 5); 

DynamicServers::ensure($amountOfServersNeeded);

So, when the wait times are long, the number of needed servers will be passed to ensure. When the queues are empty, $amountOfServersNeeded will be zero. When zero is passed to ensure, all dynamic servers will be destroyed.

Of course, this logic should be executed frequently. The package has a method determineServerCount which will be executed every minute through a scheduled command. You would typically use it in a service provider:

use Laravel\Horizon\WaitTimeCalculator;
use Spatie\DynamicServers\Facades\DynamicServers;
use Spatie\DynamicServers\Support\DynamicServersManager;

// in some service provider

DynamicServers::determineServerCount(function (DynamicServersManager $servers) {
	$waitTimesOfAllQueues = (WaitTimeCalculator::class)->calculate();
	
	$maxWaitTime = max($waitTimesOfAllQueues);
	
	// 1 server for every 5 minutes of wait time
	$amountOfServersNeeded = floor($waitTimesOfAllQueues / 60 / 5); 
	
	DynamicServers::ensure($amountOfServersNeeded);
});

Suppose you don’t want to possibly wait for a minute until the next invocation of the schedule. In that case, you could additionally listen to Horizon’s LongWaitDetected event and immediately execute the logic above to increase the server count.

In closing

The package contains a lot more features that were not mentioned in this blog post, such as using multiple server types, rebooting servers, setting a server limit, and much more.

You can see the source code of laravel-dynamic-servers in this repo on GitHub.

Should you send a large campaign via Mailcoach, you can be sure that a couple of servers will be spun up for you behind the scenes.

A special thanks to my colleague Rias who picked up this idea from Jmac’s recent streams and blog post on spawning workers based on queue workload.

This isn’t the first package that our team has built. On our company website, check out all our open source packages in this long list. If you want to support us, consider picking up any of our paid products.

Laravel News Links