This year proved multiple new use cases for e-commerce. A huge amount of businesses go through rapid digitalization and are actively looking for new tools.
We've recently seen a growing need for technologies enabling e-commerce processes for niche industries, that can be enabled in a SaaS model. While Spree Commerce gives us ready-made e-commerce processes, another aspect to consider is how to scale that across multiple customers. That's where multi-tenant may come in handy.
Use Cases for Multi-Tenant e-Commerce Applications
There are two major scenarios for creating multi-tenant applications for e-commerce:
- Creating an isolated environment for each client of the platform, that allows them to run their e-commerce stores and related processes. This allows each client to have a different product offering, shipment methods, and other configuration items.
- Creating separate environments with different products or configuration (for example for international brands where product offering and business structure differs by geographical region
Muti-tenant however won't help run multivendor marketplaces, where we want to display products from multiple vendors together.
Tools for multi-tenancy in Spree Commerce
Fortunately, there's a great solution for multi-tenancy for Rails applications called Apartment. It provides base tools for creating isolated environments via separate databases(or schemas in Postgres) as well as for switching between tenants based on domain or request headers. It's also fairly customizable, so you can implement your strategies for selecting tenants.
For Spree-based stores, the easiest way to integrate with Apartment is via Spree Shared gem. It provides a basic wrapper for Apartment, that also takes into consideration Spree's built-in mechanisms for multi-store setups.
Major Problems Solved
A common approach for creating isolated environments for each client is to run a separate application instance for each of them. This works well in small setups with a few clients, but once this number grows to hundreds, you'll run into multiple issues.
Running separate application instance for each client:
Running multiple tenants in a shared environment:
Running and supporting multiple versions of code
When running a separate instance of the same application for each client, you'll quickly run into a problem when rolling out a new update requires a lot of time. At some point, the codebase may also diverge (for example when an important client asks for a feature and it's "easier" to create a separate branch of code just for them. You may also run into a situation when someone didn't deploy the latest update to one of the clients for some reason.
Running a multi-tenant application naturally forces us to use a single codebase across all clients. In long run, this means lower maintenance and support costs.
Cost efficiency of infrastructure
When running separate instances of the same application for each client, adding more clients usually means adding more servers. In most cases, this means reserving more computing capacity than is required.
Multi-tenant setups use shared infrastructure while allowing to run a large number of virtual instances on a single server. The need for adding more instances comes from increasing traffic, not from an increasing amount of installations.
The effort required for setting up a new environment
Even with a good amount of automation, provisioning new servers for new application instances take a lot of time. It also means maintaining an increasing amount of configurations, for each new environment.
In the simplest scenario, creating new tenants in a multi-tenant setup is a matter of creating a new database entry with the configuration for the tenant. This makes it easier to onboard new tenants and also makes it easier to automate the process.
A single entry point to the admin panel
Provisioning new application servers require creating separate DNS entries for each of them. With a multi-tenant setup, you can use a single wildcard DNS record that will be routed to the application server. The server will then pick the right tenant automatically based on e.g. the subdomain used in the request. For large installations, this is a simpler approach that doesn't require additional automation for managing DNS entries.
Concerns to Look Into:
While there are ready-made solutions for supporting multi-tenancy in Spree-based applications, there are some aspects that you need to think about ahead as it will affect how you configure both the Apartment gem and the infrastructure.
As the amount of requests increases, scaling multi-tenant apps need to be considered looking at multiple angles:
- Spree/Rails application - which can be scaled horizontally by adding more nodes and configuring load balancing. This applies both to webserver as well as background jobs (e.g. Sidekiq).
- Database and cache - unlike the application server, these aren't that easy to scale horizontally. With cloud hosting it's fairly easy to increase machine RAM and CPU power, which should be sufficient. For really heavy workloads, you may need to think about introducing a multi-database setup.
Depending on SLAs, you may want to create a setup where some clients get dedicated infrastructure to ensure the best performance and reliability.
One of the choices you will face when designing a multi-tenant solution is how to structure databases to isolate tenants. There are two approaches:
- Single database server, with separate databases for each tenant (or separate schemas in Postgres)
- Multiple database servers, databases for tenants can be spread across many servers
Apartment gem supports both approaches however when configured for multiple database setup it may have performance issues as switching tenants, which may happen often, will require establishing a new database connection. Depending on network infrastructure, this may cause considerable performance degradation compared to switching databases within the same server. Another issue you will face is in multi-threaded servers, you may encounter race conditions as Rails’ connection pool is not thread-safe. Because of that, you will need to switch to a single-threaded mode.
While multi-tenant deployments aim at having a single infrastructure capable of running multiple isolated client environments, there are some situations where it won't be enough by itself.
The most common scenario is when due to data-processing laws (for example servers and data of EU -based clients should remain in the EU), you may still need to create separate "clusters" in separate infrastructures. Then, based on the requirements, you can have a master application routing clients to the right cluster based on tenant parameters.
Adding new tenants
A huge advantage of solutions like Apartment and Spree Shared is that adding new tenants can be done programatically. What you need to remember is that the preferred way of loading tenant configuration is to do that in the application's initializers. This will require you to restart the application after adding new tenants for them to become visible.
For a fully self-service model, where new tenants can be created for example via a web panel, you'll need to automate the process of restarting servers (in our experience, a proper configuration of k8s + Ingress can do a great job here).
Another aspect is that creating a new tenant can also require additional provisioning in external services. For example, if you're offering your tenants an option to have a recommendations engine provided by Google Recommendations AI, the process for creating a new tenant will also require you to provision relevant resources & service accounts in GCP. Because of that, creating a new tenant can easily turn into a multi-step long-running process that you need to plan for.
Separating general vs configuration specific to tenant
Applications running in multi-tenant setup usually have two classes of configuration:
- A global configuration shared between tenants - for example, credentials to shared external services like Twilio or Elasticsearch. They can be provided for example via env variables to the application.
- Tenant-specific configuration - for example configuration of Google Analytics integration, when Google Analytics can't be shared between tenants. The easiest way to manage it is to use Spree's Preferences model that is also isolated between tenants.
When designing multi-tenant applications, you need to review configuration items and integrations and decide on which of them go to each bucket. Be especially careful with external services - sharing a single account in e.g. Twillio may be more cost-effective, but it may also make it difficult to do a cost-breakdown between tenants.
In our experience, multi-tenancy provided by Apartment gem in Spree is a great tool for building e-commerce applications that require creating isolated environments for clients.
While it's fairly simple to do a basic integration, there is a number of parameters that need to be taken into account while designing such solutions. Contact me if you'd like to know more!