MHRubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
MHRubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
EngineeringBackend DevelopmentJune 19, 20264 min read

Implementing Laravel Multi-Tenancy with PostgreSQL Schemas

Master Laravel multi-tenancy using PostgreSQL schemas. Learn to implement dynamic database connection switching for secure, scalable SaaS architecture.

laravelpostgresqlmulti-tenancysaasphpdatabase
Interior view of contemporary multi-storey car park in Leiden, featuring unique design elements.

During a routine deployment of a new client workspace last Tuesday, we hit a wall where one tenant's request bled data into another's session. Our initial attempt at building a multi-tenant SaaS platform with Laravel 11 focused heavily on row-level security, but we realized that for true compliance and data isolation, we needed to move to a database-per-tenant model using PostgreSQL schemas.

Why PostgreSQL Schemas for SaaS Architecture

When you're scaling a SaaS product, the "one database to rule them all" approach becomes a liability. Using PostgreSQL schemas allows you to maintain a single database instance while keeping data physically separated at the schema level. It's the sweet spot between full database isolation (which gets expensive) and row-level security (which is prone to developer error).

We found that by using schemas, we could run migrations per tenant without impacting the global public schema. However, this introduced a significant challenge: how do we trigger a dynamic database connection switch mid-request?

Implementing Dynamic Database Connection Switching

Close-up of yellow fiber optic cables in a network server, showcasing fast data transfer.

Laravel's database manager isn't designed to swap schemas on the fly out of the box. We first tried using a simple DB::statement("SET search_path TO tenant_name") in a middleware, but that failed because Laravel’s connection pool cached the metadata. Every subsequent query in the same request cycle was still hitting the public schema.

We eventually landed on a solution that modifies the connection configuration at runtime. Here is how we handle the switch:

PHP
public function setTenant(Tenant $tenant)
{
    config(['database.connections.tenant.schema' => $tenant->schema_name]);
    
    DB::purge('tenant');
    DB::reconnect('tenant');
    
    Schema::connection('tenant')->getConnection()->statement(
        "SET search_path TO {$tenant->schema_name}"
    );
}

This approach works because DB::purge forces Laravel to drop the existing connection instance. When the next query executes, DB::reconnect builds a fresh connection using our updated search_path. It took us roughly 280ms to perform this handshake on our staging environment, which is acceptable for a cold start, though we're still monitoring if this adds overhead during high-concurrency traffic.

Managing Tenant Isolation and Migrations

The beauty of this architecture is that your migrations become simple. You can iterate through your tenants table and run the migration command for each schema:

Bash
foreach (Tenant::all() as $tenant) {
    Artisan::call('migrate', [
        '--database' => 'tenant',
        '--path' => 'database/migrations/tenant',
        '--force' => true
    ]);
}

However, don't forget that your public schema still needs to exist to store the shared tenants lookup table. We've had to be extremely careful about where our models point. Any model dealing with global data must explicitly use the public connection, while tenant-specific models remain connection-agnostic or rely on the tenant default.

Lessons Learned and Trade-offs

Scrabble letter tiles spelling 'trade' on a wooden surface, illustrating business concepts.

We initially tried to handle this via a custom DatabaseManager extension, but it was overkill. The DB::purge method, while slightly "hacky," is significantly easier to maintain. We also had to ensure that our Kubernetes security policies allowed for the increased number of connection handshakes, as our database proxy started flagging the rapid reconnects as potential anomalies.

If I were to do this again, I’d probably look into a dedicated package for multi-tenancy, as managing the connection state manually is fraught with edge cases—especially when dealing with queued jobs. We’re still seeing occasional issues where a background worker picks up a job without the correct schema set, leading to "Table not found" errors.

FAQ

Is PostgreSQL schema isolation secure enough for HIPAA? It provides logical isolation. While it's better than shared tables, most auditors prefer physical database isolation for high-compliance environments.

How does this impact performance? The DB::purge and reconnect calls add a small latency penalty. In our tests, it’s around 20-50ms per request, which is usually negligible compared to the query time.

Can I use this with Eloquent? Yes, but you must ensure your models are configured to use the tenant connection. It's best practice to define a TenantModel base class that sets the connection property to tenant.

This implementation isn't perfect, and we're currently exploring whether we need to move to a full database-per-tenant model to simplify our backup and restore pipelines. For now, schemas provide the balance we need, even if the connection management keeps us on our toes.

Back to Blog

Similar Posts

Close-up of a vintage typewriter with a paper displaying 'WordPress', ideal for blogging and writing concepts.
EngineeringJune 19, 20264 min read

WordPress Database Optimization: Implementing HyperDB for Scaling

Master WordPress database optimization with HyperDB. Learn to implement read-write splitting, handle replication lag, and scale your MySQL infrastructure safely.

Read more
Close-up of a vintage typewriter with paper labeled Wordpress.
Engineering
June 19, 2026
3 min read

WordPress Performance: Implementing Redis Persistent Object Caching

Boost WordPress performance with Redis object caching. Learn to configure WP-Redis and W3 Total Cache to slash database queries and scale your site effectively.

Read more
Close-up of a vintage typewriter with a paper displaying 'WordPress', ideal for blogging and writing concepts.
EngineeringJune 19, 20264 min read

Mastering Headless WordPress: Next.js ISR with WPGraphQL

Learn how to implement headless WordPress using Next.js ISR and WPGraphQL. Optimize frontend performance and solve cache invalidation issues in production.

Read more