Many engineers still confuse Single-Tenant Databases with Multi-Tenant Databases when discussing cloud-native systems like Microservices. They often think of a database as just a container they can request on-demand, forgetting that a database is a valuable asset that requires proper care—especially while keeping costs manageable. In this short article, I will tell you, how you should take care of your lovely database.
It is all about Performance
Youssif is a skilled software engineer who designs systems capable of supporting operations for many years. One day, he was discussing with his friend Alex the database design they needed to consider for a new system expected to scale rapidly.
Alex said, "We are building cloud-native systems, so we must follow the database-per-service pattern to avoid the known issues associated with shared databases in microservices."
Youssif replied, "That's a good idea. What do you think, Alex? What should we ask the DevOps team to prepare for us?"
Alex said, "Since we're using AWS, we should request multiple RDS instances. Let’s start with the initial design and provision nine RDS instances to serve nine microservices applications."
Youssif said, "I don't think that's the right approach. We should first talk to the team to understand more about the system's qualities and the performance they expect. If their operations aren't too demanding and they’re constrained by budget, I’d prefer starting with a single RDS instance but using multiple databases or schemas. What do you think?"
Alex: "That's a good idea!"
Various Methods for Constructing the Pattern
Thinking about design patterns is like teaching a student. You want to help them learn something from a book, but every teacher has their own unique way of teaching. In the end, all of them ensure that the student understands the concept presented in the book.
The High-Level Design (HLD) shown above represents the initial architecture for a ride-sharing application similar to Uber. The team decided to use an Oracle database for their needs, for whatever reason. Given that Oracle licensing costs are significantly high, does it make sense to ask the DevOps team to provision multiple Oracle servers for the application? No
But they need to build a feature to manage organization offers—something resembling multi-tenancy. Did you forget the pattern? No, I didn’t. But did you forget the teacher example I shared with you?
Of course, they can provide multi-tenant support without violating the pattern by leveraging Oracle's multi-database features. This approach offers several benefits:
- Resource Optimization: Requires fewer hardware and infrastructure resources.
- Cost Efficiency: Significantly reduces Oracle licensing and operational costs.
- Improved Code Design: Encourages cleaner and more maintainable architecture.
All they need is to make every microservice accessing specific database. The setup might look something like this:
Microservice 1 → Database 1
Microservice 2 → Database 2
Microservice 3 → Database 3
...
Microservice N → Database N
They can also manage database creation dynamically with something like this:
CREATE TABLE tenants (
tenant_id VARCHAR(50) PRIMARY KEY,
db_url VARCHAR(255),
db_username VARCHAR(50),
db_password VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
And a nice dynamic code that manages tenants over DB
public class TenantService {
private final JdbcTemplate adminJdbcTemplate;
public TenantService(JdbcTemplate adminJdbcTemplate) {
this.adminJdbcTemplate = adminJdbcTemplate;
}
public void createTenant(String tenantId) {
String dbName = "tenant_" + tenantId;
// Step 1: Create Database
adminJdbcTemplate.execute("CREATE DATABASE " + dbName);
// Step 2: Initialize Schema
JdbcTemplate tenantJdbcTemplate = new JdbcTemplate(
DataSourceBuilder.create()
.url("jdbc:mysql://localhost/" + dbName)
.username("root")
.password("password")
.build()
);
tenantJdbcTemplate.execute("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100));");
// Step 3: Save Metadata to Admin DB
adminJdbcTemplate.update(
"INSERT INTO tenants (tenant_id, db_url, db_username, db_password) VALUES (?, ?, ?, ?)",
tenantId, "jdbc:mysql://localhost/" + dbName, "root", "password"
);
}
}
Conclusion
Designing databases for cloud-native systems requires a careful balance between scalability, cost efficiency, and maintainability. While the database-per-service pattern is ideal for avoiding the pitfalls of shared databases in microservices, its implementation must consider the system’s performance requirements and budget constraints. Ultimately, the choice of database architecture should be guided by the system’s unique needs. By thoughtfully applying design patterns and leveraging features like dynamic provisioning, teams can build robust, scalable, and efficient cloud-native applications. Remember, the goal is not just to follow a pattern but to adapt it to ensure the system operates effectively within its constraints.
Top comments (0)