Back in the old days, when PHP was in its infancy – deployment details were “left as an exercise to the reader,” in this case, developers. There was no standardized deployment model, guidelines, or best practices on how deployment should be done. Early on, in the vast majority of cases – deployment simply meant copying the updated PHP files to the right directory in the server – relying on PHP’s behavior to instantly start using these new files.
As PHP and the types of applications it’s being used for matured, this simplistic approach started showing its limits, especially for business-critical apps. The problem had to do with the fact that PHP loads individual files as they are needed, and PHP applications typically involve hundreds, and sometimes even thousands of files in the context of a single request. Therefore, the files in a request may change during execution, resulting in the request beginning to execute with one set of files, and continuing execution with a different set of files. In some cases it could even result in a partially-changed file (one that has not yet been fully updated) being loaded. Depending on the nature of changes in the new version and the specific point in time the app happened to load specific individual files, this could result in nothing at all, fatal errors, and or possibly execution of flawed logic which arguably is the most problematic outcome.
In addition to not providing atomicity, these simplistic mechanisms also provided no mechanisms for management (which version is deployed?), rollback, or cross-cluster synchronization.
When we released the Zend deployment technology as a part of Zend Server, it aimed to solve these drawbacks. It placed emphasis on being atomic – apps would never be served by a mix of both new and old files, it allowed for easy management of apps supporting installation, updating, and rollback workflows – and provided seamless cross-cluster synchronization.
However, the atomic nature of the Zend deployment technology came at a cost – it required a web server restart for every application update. Initially, this was a reasonable cost to pay, as application updates weren’t that frequent, and Zend Server also provided ways to ensure that at any given time, only a small subset of the servers in the cluster would be undergoing a restart – so the cluster as a whole continued responding properly.
With the growing popularity of continuous delivery, it became increasingly problematic to require web server restarts for every update. Therefore in Zend Server 9.1, we’ve rolled out a new mechanism that allows for zero downtime application updates, not just at the cluster level – but at each server level too.
How deployment use to work
To understand how it works, let’s first look at how the deployment mechanism handled updates before Zend Server 9.1 (9.0 and earlier). The following is a high-level, non-exhaustive description:
- The archive containing the new version is extracted into a new location on the server’s filesystem.
- The web server configuration is updated to point to the new location.
- The web server process (Apache or nginx) is restarted for the new configuration to take effect.
The same process was repeated in every server in the cluster, at a configurable level of parallelism – in order to ensure that not all servers in the cluster are restarted at the same time, so that the cluster as a whole continues to respond.
How zero downtime deployment works
The zero downtime approach tackles the job a bit differently. First, from the very first deployment, the web server is configured not to point to the actual location on the filesystem where the application files reside but rather, to a symbolic link, which in turn points to the application files’ location.
When an update is requested, the following steps are performed:
- The archive containing the new version is extracted into a new location on the server’s filesystem (identical to the previous method).
- The symbolic link pointer is moved to point to the new application location on the filesystem. The web server configuration remains untouched.
This is simple enough, but in order to ensure a fully atomic behavior, there’s one extra step that needs to happen. Without it, it’s still possible for requests to begin executing with an old set of files, and continue execution with a new set of files – with all of the potential risks associated.
To be truly atomic, we must ensure that there’s a way for every request to finish execution using the same set of files it began execution with, and thankfully,
zend_deployment_application_path() API does exactly that.
Regardless of when it’s called during the request lifetime, this API will always return the base path from which the request began executing – even if a new version was deployed in the meantime. Using this path as the base path for loading all subsequent PHP files into memory ensures that we’re using just one set of files the entire time. And given most PHP applications nowadays use autoloaders, introducing this base path across the entire application typically involves only minor changes to that single autoloading function.
The new deployment strategy in Zend Server 9.1 allows companies to update their applications a lot more frequently and without affecting end-user service. All servers remain working in full capacity without interruption or performance degradation. As such, it’s a perfect fit for continuous delivery, as well as any other scenario where frequent updates are needed.
While the ‘last mile’ of ensuring the atomic nature of updates requires a tiny bit of manual intervention (in the form of one-time minor code changes), the outcome is well worth it, and we recommend all our customers switch to this strategy.
As always, our teams are here to help if you have any questions or need any help or guidance implementing this!
See what else is new in Zend Server 9.1.