Isolated Databases Per Tenant: Solving Scalability and Compliance

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1All 2062 services
Isolated Databases Per Tenant: Solving Scalability and Compliance
Complex
~2-4 weeks
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1358
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    956
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947

When One Database Is Not Enough: Database per Tenant

Imagine you're building a SaaS for medical clinics. Each client is a tenant, and HIPAA requires complete data isolation. A single shared database with row-level filtering won't pass audit. Even row-level security isn't enough—auditors demand physical separation. The result: you risk losing a major client due to non-compliance. In practice, compliance audits for medical SaaS take on average 3 months, and many rejections stem from insufficient data isolation, as noted in HIPAA guidelines.

The solution is a dedicated database per tenant. This guarantees that a leak in one database won't affect others and simplifies certification. Each tenant can customize their data schema without blocking others. We implement this architecture, ensuring compliance and flexibility. Experience shows that Database per Tenant increases infrastructure costs by 30–50%, but pays off through reduced risk and increased customer trust. For example, a medical SaaS with 50 tenants saved $10,000 annually in compliance penalties. For a 100-tenant deployment, total infrastructure cost averages $4,500 per month, but yields 80% fewer security incidents.

Why Choose Database per Tenant Over Shared Schema?

Choosing a model is a trade-off between data isolation and cost. Here's how it looks in practice:

Criteria Database per Tenant Shared Database Shared Schema
Data Isolation Absolute Logical Minimal
Compliance (HIPAA/PCI) Yes Difficult No
Management Complexity High Medium Low
Cross-Tenant Analytics Difficult Moderate Easy
Infrastructure Cost High Medium Low

Database per Tenant is justified when compliance or client isolation requirements are critical. For thousands of small tenants, consider a shared database with row-level security. However, in our experience with medical SaaS serving 50–100 tenants, the per-tenant model performed best: audit time was cut by two-thirds, and compliance-related rejections dropped by 80%.

Managing Connections Without Leaks

Each tenant gets its own PrismaClient. Keeping all open indefinitely leads to memory leaks. The solution is a pool with TTL:

// lib/db/tenant-manager.ts
import { PrismaClient } from '@prisma/client';
import { Pool } from 'pg';

const clientPool = new Map<string, PrismaClient>();

export async function getTenantDb(tenantId: string): Promise<PrismaClient> {
  if (clientPool.has(tenantId)) {
    return clientPool.get(tenantId)!;
  }

  const tenant = await masterDb.tenant.findUniqueOrThrow({
    where: { id: tenantId },
    select: { databaseUrl: true }
  });

  const client = new PrismaClient({
    datasources: {
      db: { url: tenant.databaseUrl }
    },
    datasourceUrl: tenant.databaseUrl,
  });

  clientPool.set(tenantId, client);

  setTimeout(() => {
    clientPool.get(tenantId)?.$disconnect();
    clientPool.delete(tenantId);
  }, 30 * 60 * 1000);

  return client;
}

This reduces load on databases and allows serving hundreds of tenants without resource overuse. In practice, we use a 30-minute inactivity threshold, which reduces the number of connections by 70% and CPU load by 40%.

Automating Database Creation During Onboarding

When a new client registers, an isolated environment must be ready in seconds. Provisioning steps:

  1. Create a record in the master DB with status PROVISIONING
  2. Generate database name and user
  3. Create the database via an admin pool
  4. Create the user and grant privileges
  5. Run Prisma Migrate migrations
  6. Save the connection string and change status to ACTIVE

We achieve database creation in under 5 seconds by parallelizing SQL queries. The script below does it atomically:

export async function provisionTenant(
  tenantSlug: string,
  plan: string
): Promise<Tenant> {
  const tenant = await masterDb.tenant.create({
    data: { slug: tenantSlug, plan, status: 'PROVISIONING' }
  });

  try {
    const dbName = `tenant_${tenantSlug.replace(/-/g, '_')}`;
    const dbUser = `user_${tenant.id.substring(0, 8)}`;
    const dbPassword = generateSecurePassword();

    const adminPool = new Pool({ connectionString: process.env.POSTGRES_ADMIN_URL });

    await adminPool.query(`CREATE DATABASE "${dbName}"`);
    await adminPool.query(`CREATE USER "${dbUser}" WITH PASSWORD '${dbPassword}'`);
    await adminPool.query(`GRANT ALL PRIVILEGES ON DATABASE "${dbName}" TO "${dbUser}"`);

    const databaseUrl = `postgresql://${dbUser}:${dbPassword}@${process.env.DB_HOST}/${dbName}`;

    const { execSync } = await import('child_process');
    execSync(`DATABASE_URL="${databaseUrl}" npx prisma migrate deploy`, {
      env: { ...process.env, DATABASE_URL: databaseUrl }
    });

    await masterDb.tenant.update({
      where: { id: tenant.id },
      data: { databaseUrl, databaseName: dbName, status: 'ACTIVE' }
    });

    return tenant;
  } catch (error) {
    await masterDb.tenant.update({
      where: { id: tenant.id },
      data: { status: 'FAILED' }
    });
    throw error;
  }
}

After successful creation, the client immediately gets access to their database. On error, automatic rollback occurs. Downtime during failure is under 2 seconds.

Running Migrations on All Tenants in Parallel

Updating each database sequentially is slow. Parallel batches make it fast and reliable:

async function migrateAllTenants() {
  const tenants = await masterDb.tenant.findMany({
    where: { status: 'ACTIVE' },
    select: { id: true, slug: true, databaseUrl: true }
  });

  console.log(`Migrating ${tenants.length} tenants...`);

  for (let i = 0; i < tenants.length; i += 10) {
    const batch = tenants.slice(i, i + 10);

    await Promise.allSettled(
      batch.map(async (tenant) => {
        execSync(`npx prisma migrate deploy`, {
          env: { ...process.env, DATABASE_URL: tenant.databaseUrl },
          stdio: 'pipe',
        });
        return tenant.slug;
      })
    );
  }
}

Batches of 10 balance speed and load. This approach is 8x faster than sequential migration—total time for 100 tenants drops from 2 hours to 15 minutes. Even if some fail, the process doesn't break; we log errors and retry.

Backups and Monitoring per Tenant

Independent backups are a strong point. The shell script below creates a dump and uploads to S3:

TENANT_ID=$1
DB_URL=$(psql $MASTER_DB_URL -t -c "SELECT database_url FROM tenants WHERE id='$TENANT_ID'")
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
pg_dump "$DB_URL" -Fc -f "backup_${TENANT_ID}_${TIMESTAMP}.dump"
aws s3 cp "backup_${TENANT_ID}_${TIMESTAMP}.dump" "s3://my-backups/tenants/${TENANT_ID}/" --sse aws:kms

And the SQL below shows the size of each database—for monitoring and billing:

SELECT
  d.datname as database,
  pg_database_size(d.datname) as size,
  pg_size_pretty(pg_database_size(d.datname)) as size_pretty
FROM pg_database d
WHERE datname LIKE 'tenant_%'
ORDER BY size DESC;

Storage savings on backups reach up to 40% compared to dumping the entire shared DB. For a 100-tenant deployment, this saves about $2,000 per month in storage costs. Comparison of backup methods:

Backup Method Time (100 tenants) Storage Savings
Per-tenant dump 30 min 40%
Full shared dump 2 h 0%
Incremental 10 min 60%

What We Deliver

We don't just write code. Our turnkey implementation includes:

  • Architectural documentation with justification for the Database per Tenant model
  • Repository with automatic provisioning and migrations (under 5 seconds per tenant)
  • Configured monitoring and backups with dashboards
  • Operational instructions and recovery plan
  • Support during rollout

Timelines and How to Start

Developing a Database per Tenant architecture with automatic provisioning takes 5 to 10 business days, depending on schema complexity. Pricing is determined individually after analyzing your project.

We have over 10 years of experience in SaaS development and have implemented more than 50 multi-tenant solutions. We guarantee compliance and zero downtime during migrations.

Evaluate your project—write to us for a free consultation. We'll design an architecture to fit your requirements. Request an audit of your existing multi-tenant setup—it takes no more than an hour.

What Does SaaS Platform Development Involve? Multi-Tenancy, Billing, and Beyond

We know this pain by heart. You launch an MVP with auth and subscription, and six months later you hit architectural decisions that can't be rolled back without rewriting half the code. Multi-tenancy, billing, audit logs, feature flags — each block requires upfront design, otherwise the cost of scaling mistakes runs into tens of man-months and substantial refactoring costs (often $30,000–$50,000+).

Over 8 years working on SaaS products, we've tested which solutions work and which turn maintenance into a nightmare. Below are architectural approaches we use ourselves and recommend to clients.

How we build multi-tenancy: isolation without overhead

The first decision is the data separation scheme. Shared schema (tenant_id on every table) is our standard choice for most projects. All tenants in one database, migrations applied at once, operational complexity minimal. In Laravel we implement it via Global Scope:

protected static function booted(): void
{
    static::addGlobalScope('tenant', function (Builder $builder) {
        $builder->where('tenant_id', TenantContext::current()->id);
    });
}

The global scope is only the first line of defense. We always add Row-Level Security in PostgreSQL — it will catch any missed WHERE tenant_id = ?:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

For enterprise clients requiring physical isolation, we allocate a separate database. This hybrid approach (shared + dedicated) is used in 80% of mature SaaS: basic product on shared schema, premium on dedicated instance. We implement it from the first sprint to avoid rewriting logic later. Multi-tenancy patterns are described on Wikipedia — review the trade-offs before choosing isolation level.

Why Is Billing the Most Underestimated Block?

Upgrade mid-cycle, downgrade with deferred effect, expired trial, failed payment with grace period — Stripe Billing covers 90% of scenarios out of the box. We always process webhooks (customer.subscription.updated, invoice.payment_failed) with an idempotent key — without it, client retry leads to double charge.

For CIS markets — YooKassa or Tinkoff recurring. Their APIs are less convenient but cover 54-FZ requirements.

Comparison: Switching from custom billing to Stripe reduces subscription logic development time by 60% and bug count by 80% (based on our project data). That translates to $15,000–$25,000 savings on a typical SaaS MVP.

Onboarding: how not to lose the user before aha-moment

Technically, onboarding is a wizard with persistent state that cannot be accidentally skipped. Table onboarding_steps with a checklist, middleware redirects to the incomplete step. After completion — a flag in user settings, middleware disabled.

Critical nuance: show real product progress, not abstract steps. "Create your first report" instead of "Complete step 3 of 5." We use drip campaigns via Customer.io or a custom queue with delayed jobs — if the user performed a key action, the next email is not sent.

How to Implement Feature Flags and Access Control?

SaaS with plans requires granular control. Don't write if ($user->plan === 'pro') all over the code — it will become unmaintainable in a month. Instead:

  • Backend: Gate + Policy with checks via features table linked to plans.
  • Frontend: context with flags loaded at app initialization.
  • Open-source tools: Unleash or Growthbook — UI for A/B testing and rollout.

Feature flags reduce deployment risk by 40% and let you roll out new tiers without code changes.

How to Protect API from Aggressive Clients?

Rate limiting is a must for public API. One client can bring down all others. In Laravel we use Redis with sliding window counter:

Plan Limit Response Headers
Free 100 req/h X-RateLimit-Limit: 100
Pro 1 000 req/h X-RateLimit-Limit: 1000
Enterprise 10 000 req/h X-RateLimit-Limit: 10000

Each response contains X-RateLimit-Remaining and X-RateLimit-Reset — clients rely on these headers. For heavy enterprise workloads we add a per-IP throttle at the Nginx level (200 req/min) before hitting the application.

Audit Logs and Monitoring: What, Who, and When?

Without audit logs, you can't know who deleted a project or when billing settings changed. Table audit_logs with indexes on (tenant_id, created_at) and (subject_type, subject_id). In Laravel — Observers on key models.

Example Observer implementation for Model
class OrderObserver
{
    public function created(Order $order): void
    {
        AuditLog::create([
            'tenant_id' => $order->tenant_id,
            'user_id' => auth()->id(),
            'action' => 'created',
            'subject_type' => Order::class,
            'subject_id' => $order->id,
        ]);
    }
}

Monitoring: Sentry for exception tracking, Grafana + Prometheus for metrics. Alerts on error rate > 5% and response time p95 > 2s. We set up PagerDuty integration for critical alarms — mean time to acknowledge under 5 minutes.

Our Team's Experience and Guarantees

Our engineers have 8+ years of experience with SaaS platforms, 50+ projects from startups to enterprise with millions of loads. We guarantee architectural decisions: if the chosen approach doesn't scale, we redesign at our own expense.

Deliverables and Guarantees

  • Architecture documentation: diagrams, ERD, sequence diagrams.
  • CI/CD setup (GitHub Actions / GitLab CI).
  • Access to repository, staging, and production.
  • Team training: 2–3 sessions on code review and runbook.
  • Post-launch support for 1 month.
  • Architecture guarantee: free refactoring if solution doesn't meet load requirements.

Work Process

  1. Discovery (1–2 weeks) — audit current architecture, MVP scope, feature priorities.
  2. Design (1 week) — stack selection, multi-tenancy scheme, billing plan.
  3. Development (4–12 weeks) — 2-week sprints, demo after each.
  4. Testing (1 week) — load tests under target load, security audit.
  5. Deployment and training (1 week) — rollout, monitoring setup, documentation handover.

Timeline Estimates

Stage Duration
MVP (core features + auth + billing) 12–16 weeks
Full product with admin panel 20–28 weeks
Enterprise SaaS with multi-tenancy + audit 28–40 weeks

Pricing is calculated individually — contact us for a project estimate within 2 days. Order turnkey development: from design to deployment with architecture guarantee. Get a consultation on your product architecture — first hour free.