PHP at Scale #10
Welcome to the tenth edition of PHP at Scale. I am diving deep into the principles, best practices and practical lessons learned from scaling PHP projects — not only performance-wise but also code quality and maintainability.
June 2025 - 30 years ago, PHP was released. Before I jump into some technical things, let me start with some old memories. What was your first contact with PHP? For me, it was back 90s. Back then, in Poland, we had to use dial-up internet (it made a hilarious noise when connecting), blocking the landline and costing a fortune. Most of my PHP experience was based on a PHP 3.0 book and coding offline in a notepad or something like that.
If you are an “older” PHP developer, I think you will enjoy this PHP history timeline. It’s 5 years old, but we (Accesto) are now working on an updated version.
But putting the lovely memories aside, and focusing on some things that will actually help you scale PHP-based software, let’s take today as the topic of multi-tenancy.
Multi-tenancy is not required in all projects, but it definitely should be in a lot of them. My day-to-day work is focused on SaaS projects, where this is, in most cases, required.
This one might be obvious to most of you, but I still need to touch on that topic. Multi-tenancy means that the software you write can handle multiple customers/tenants on one instance of your app. So one codebase, one set of hardware, serves multiple tenants (accounts).
It is easier to understand if you grasp single-tenant architecture. This means that if a new customer buys your SaaS, you need to set up a fresh instance for them. I’ve even seen companies creating a new git repository for new tenants, as they had to adjust some settings for them, or switch some things in the layout. This usually becomes problematic long-term.
For most SaaS, going multi-tenant is the better choice.
You can probably find longer articles, but this one works for me quite well. It is the most basic knowledge you need to have when starting a discussion on multi-tenancy.
I often find people implementing multi-tenancy using an external library, without the basic knowledge, like the three models.
In my opinion, the best approach is to consider: how secure you need the data, how big each tenant is, and how many tenants you have. In the security part, you also need to consider requirements enforced by the law. Bridge seems to be the most common approach, looking at available libraries. It allows some good isolation, but keeps the costs lower. I don’t like the idea of treating it as “default”, as people tend to skip the disadvantages it has. You need to handle database migrations per tenant, and also switch the app to the right database - this is not hard, but sometimes gets a bit specific when running backend queues and workers.
In some cases, a single-tenant app with proper infrastructure automations will fit perfectly. It all depends on the scale, type of tenants, and the business model.
When picking the pool approach, have a look at tools like Postgres Row Level Security.
If you are on Laravel, this package seems to be very popular. It also handles lots of flows for you and allows you to switch between the pool and bridge models easily. If you are in a hurry, this definitely looks like a good option. But…
Implementing multi-tenancy on your own
I’ll be honest, in 100% of the software we wrote, we handled all the multi-tenancy on our own (also using Laravel). The reason for it is that it actually does not require that much code. A couple of classes, hooking into the request, a bit of changes to the message bus, and you have it covered. I know, it might sound like “not invented here” syndrome, but in this case, it is a very important part of the system. Developing it from scratch takes us currently less than a day. And although it seems you might save a day of development with the package mentioned above, I prefer knowing exactly how this part of the system works and being able to adjust it 100% to our case.
I’ve discussed this with multiple SaaS teams, and it is fascinating to see how similar some problems are and how different the solutions can be. Is this bad? No, each solution should ideally match some very specific business needs.
If you stick to the pool model, you can add one ORM extension that will enforce the tenant ID in all related SQL queries. Fancy right? But you can also just check if the developer added the tenant ID himself, and throw an exception otherwise.
If you think other flows are more complicated, they are not; just look at the queue example.
There are obviously some caveats, but all can be handled pretty easily. The first time is a bit harder, but once you get it, it will become easy.
Beware of dragons - what can go wrong, noisy neighbour, etc.
Of course, life will surprise you with unexpected problems. A noisy neighbour might be one category of difficulties to expect. It can happen on multiple levels whenever tenants share resources (CPU, DB performance, etc). The multi-tenant architecture you have chosen will have a huge impact on how you can overcome those obstacles. E.g. in this article, Microsoft explains a way to additionally split some tenants to different deployments. It can be used to split load, evict high-load tenants to separate environments, but can also be used to release a new feature to a subset of tenants (in this case, feature flags are also an option, usually way easier to implement).
Don’t try to solve all problems upfront. Stay calm, there is always a good solution that you can implement once it is needed.
I’ve noticed some articles love to overcomplicate things. Keep in mind who wrote the article. I linked to some AWS content above, and I like this article, but you can easily find AWS articles that will describe a super robust solution for setting up separate services using CloudFormation. Of course, it’s great, but for 99% you won’t need it, and AWS describes it as it will bump up the use of their services ;)
After reading the headline article, I still believe in many cases it is worth investing in multi-tenant systems. It's just related to how many tenants you have. If you have a couple of tenants onboarded a year, or you have 1000+ new tenants each week. This is a significant architecture driver ;)
Not only the database
Most of the articles above focused on the database part of your system. In many cases, you will unfortunately need to consider a bit more infrastructure. As already mentioned in the previous heading, overall system load also needs to be considered.
Another thing that often pops up when I discuss multi-tenant systems with many SaaS companies are queues. Queueing in multi-tenant SaaS systems is tricky, and similar to DBs, you have many options. In some cases, you can perfectly work with one worker for all tenants, sometimes you might need a worker per tenant, and sometimes you might need something in between. It is good to know what the options are, and I doubt any of the ready to be used libraries will make it easy to adjust - in this case, your own implementation might shine.
Wrap up
I hope this release of my newsletter introduced you to some new concepts that will make it easier to select the right approach when you will work on multi-tenant architectures. If you have questions or believe there is more that should be discussed - let me know in the comments!
Why is this newsletter for me?
If you are passionate about well-crafted software products and despise poor software design, this newsletter is for you! With a focus on mature PHP usage, best practices, and effective tools, you'll gain valuable insights and techniques to enhance your PHP projects and keep your skills up to date.
I hope this edition of PHP at Scale is informative and inspiring. I aim to provide the tools and knowledge you need to excel in your PHP development journey. As always, I welcome your feedback and suggestions for future topics. Stay tuned for more insights, tips, and best practices in our upcoming issues.
May thy software be mature!