December 2024. VaidyaAI Production Environment.
3:47 AM. Slack notification: "Database connection pool exhausted. 47 pending queries. Clinic registration failing."
We had just onboarded our 3rd clinic. The system was choking on concurrent prescription validations from multiple tenants.
This was my PostgreSQL vs MySQL moment of truth.
Every technical blog post claims to do "detailed comparisons" between databases. Most are theoretical garbageβbenchmarks run on synthetic datasets with zero production context.
This post is different.
I'm going to show you real performance data from VaidyaAI's production environmentβ1,100+ prescriptions processed across multiple clinics, handling sensitive patient data under HIPAA-equivalent Indian compliance standards.
By the end, you'll know exactly which database to choose for multi-tenant healthcare SaaS, and more importantly, why.
The Requirements (Healthcare SaaS Is Different)
Before diving into benchmarks, let's be clear about what healthcare SaaS actually demands from a database:
1. Multi-Tenant Data Isolation (Non-Negotiable)
When Clinic A queries patient records, they should never see Clinic B's data. Not through a bug, not through a SQL injection, not through any circumstance.
This isn't just good practiceβit's legal compliance. A data leak in healthcare means:
- βΉ5-10 lakh penalty per record (Indian Personal Data Protection Act, 2023)
- Loss of medical license for involved practitioners
- Criminal liability for founders
- Instant business death
So our database architecture must guarantee isolation at the database level, not just application level.
2. ACID Compliance (Lives Depend On It)
In healthcare, eventual consistency is not acceptable.
Consider this scenario:
- Doctor prescribes medicine X
- Pharmacist dispenses medicine X
- Transaction fails mid-way
With eventual consistency, you might end up with:
- Prescription record: "Not dispensed"
- Inventory record: "Medicine dispensed"
- Billing record: "Payment pending"
Result: Patient doesn't get medicine, but inventory shows it was given. Or worseβdouble-dispensing.
We need full ACID guarantees.
3. Complex Query Performance
Healthcare queries are not simple CRUD operations. Here's a real query from VaidyaAI:
SELECT
p.patient_id,
p.full_name,
p.age,
p.chronic_conditions,
p.allergies,
COUNT(DISTINCT rx.id) as total_prescriptions,
JSON_AGG(
JSON_BUILD_OBJECT(
'medicine', m.name,
'dosage', rxm.dosage,
'frequency', rxm.frequency,
'interaction_risk', di.severity
)
) as medication_history
FROM patients p
LEFT JOIN prescriptions rx ON p.id = rx.patient_id
LEFT JOIN prescription_medicines rxm ON rx.id = rxm.prescription_id
LEFT JOIN medicines m ON rxm.medicine_id = m.id
LEFT JOIN drug_interactions di ON (
di.drug_a = m.generic_name
AND di.drug_b IN (
SELECT generic_name
FROM medicines
WHERE id IN (
SELECT medicine_id
FROM prescription_medicines
WHERE prescription_id IN (
SELECT id
FROM prescriptions
WHERE patient_id = p.id
AND status = 'active'
)
)
)
)
WHERE p.clinic_id = ?
AND p.id = ?
GROUP BY p.patient_id, p.full_name, p.age, p.chronic_conditions, p.allergies;
This query involves:
- 5 table joins
- Nested subqueries
- JSON aggregation
- String matching for drug interactions
And it needs to run in under 500ms while a doctor is waiting to see the result.
4. Concurrent Write Performance
Multiple clinics are simultaneously:
- Creating prescriptions
- Updating patient records
- Recording medicine dispensing
- Processing payments
All these operations must happen concurrently without blocking each other.
Architecture Decision: Schema-Per-Tenant vs Shared Schema
Before comparing PostgreSQL vs MySQL, we need to decide on the multi-tenant architecture pattern.
Option 1: Database-Per-Tenant
clinic_1_database
clinic_2_database
clinic_3_database
...
Pros:
- Perfect data isolation (physically separate databases)
- Easy to backup individual clinics
- Can scale by moving databases to different servers
Cons:
- Expensive (separate connection pools for each tenant)
- Nightmare to maintain (schema migrations Γ N databases)
- Can't run cross-tenant analytics
- Connection limit issues (500 clinics = 500 databases)
Verdict: Only viable for large enterprise customers (βΉ50K+/month pricing).
Option 2: Schema-Per-Tenant (PostgreSQL Only)
vaidyaai_database
βββ clinic_1_schema
β βββ patients
β βββ prescriptions
β βββ medicines
βββ clinic_2_schema
β βββ patients
β βββ prescriptions
β βββ medicines
βββ clinic_3_schema
βββ patients
βββ prescriptions
βββ medicines
Pros:
- Good data isolation (logical separation)
- Single database to manage
- Single connection pool
- Easier migrations (still one database)
Cons:
- PostgreSQL-only feature (MySQL doesn't have true schemas)
- Slightly more complex queries (need to set search_path)
- One backup for all tenants
Verdict: Sweet spot for most SaaS applications.
Option 3: Shared Schema with Tenant ID Column
patients
βββ id
βββ clinic_id β Tenant discriminator
βββ full_name
βββ age
βββ ...
prescriptions
βββ id
βββ clinic_id β Tenant discriminator
βββ patient_id
βββ ...
Pros:
- Simplest to implement
- Works with any database
- Easy cross-tenant analytics
Cons:
- Weak data isolation (one WHERE clause bug = data leak)
- Must remember clinic_id filter in EVERY query
- Performance degrades as data grows (indexes include all tenants)
Verdict: Acceptable for low-stakes SaaS. Unacceptable for healthcare.
VaidyaAI's Choice: Schema-Per-Tenant (PostgreSQL)
We chose schema-per-tenant in PostgreSQL because:
- Security first: Data leak requires compromising both application AND database layer
- Scalability: Can move schemas to different databases later if needed
- Simplicity: Application code doesn't need to remember tenant context in every query
- Compliance: Auditors love seeing physical separation
This decision immediately ruled out MySQL (no true schema support).
Performance Benchmarks: PostgreSQL vs MySQL
Before committing to PostgreSQL, I ran identical workloads on both databases to validate the decision.
Test Environment:
- Server: Hostinger VPS (4 vCPU, 8GB RAM)
- PostgreSQL 14.5
- MySQL 8.0.32
- Dataset: 500 patients, 1,100 prescriptions, 200 medicines
- Concurrent users: 10 (simulating 3 clinics)
Benchmark 1: Complex Read Query (Patient History)
| Metric | PostgreSQL | MySQL | Winner |
|---|---|---|---|
| Average query time | 287ms | 523ms | PostgreSQL |
| 95th percentile | 412ms | 891ms | PostgreSQL |
| Query plan optimization | Excellent | Good | PostgreSQL |
Analysis: PostgreSQL's superior query planner handled the 5-way join + nested subquery much more efficiently. MySQL's optimizer struggled with the drug interaction subquery.
Benchmark 2: Concurrent Write Performance
| Metric | PostgreSQL | MySQL | Winner |
|---|---|---|---|
| Prescriptions/second | 47 | 39 | PostgreSQL |
| Lock contention | Minimal (MVCC) | Moderate | PostgreSQL |
| Deadlock frequency | 0.2% | 1.7% | PostgreSQL |
Analysis: PostgreSQL's MVCC (Multi-Version Concurrency Control) shines here. Multiple clinics can write concurrently without blocking each other. MySQL's row-level locking caused more contention.
Benchmark 3: JSON Query Performance
We store prescription details as JSON (medicines array, dosage instructions, etc.). Both databases support JSON, but performance differs:
SELECT * FROM prescriptions
WHERE medicines @> '[{"name": "Paracetamol"}]'::jsonb;
SELECT * FROM prescriptions
WHERE JSON_CONTAINS(medicines, '{"name": "Paracetamol"}');
| Metric | PostgreSQL (JSONB) | MySQL (JSON) | Winner |
|---|---|---|---|
| Query time (1000 records) | 42ms | 187ms | PostgreSQL |
| Index support | GIN index (fast) | Generated columns (workaround) | PostgreSQL |
| Operators available | @>, ?, ?&, ?|, #>, etc. | JSON_* functions only | PostgreSQL |
Analysis: PostgreSQL's JSONB type with GIN indexing is 4.5Γ faster. This matters when searching "all prescriptions containing medicine X with dosage > Y".
Query Performance: PostgreSQL vs MySQL (Lower is Better)
Benchmark 4: Full-Text Search
Doctors search patient records by name, symptoms, diagnosis. We need fast text search across multiple fields.
SELECT * FROM patients
WHERE to_tsvector('english', full_name || ' ' || chronic_conditions)
@@ to_tsquery('diabetes & hypertension');
| Feature | PostgreSQL | MySQL |
|---|---|---|
| Built-in FTS | β Yes (tsvector, tsquery) | β FULLTEXT index only (limited) |
| Ranking support | β ts_rank(), ts_rank_cd() | β No native ranking |
| Multi-language | β 20+ languages including Hindi | β οΈ Limited (Latin script bias) |
| Search time (500 patients) | 23ms | 94ms |
Analysis: PostgreSQL wins decisively. For Indian healthcare, Hindi language support is crucial (many patient notes in Devanagari).
Real-World Production Metrics
Benchmarks are useful, but production reality is different. Here's VaidyaAI's actual performance after 3 months in production:
Pain Points We Hit (And Solved)
Problem 1: Connection Pool Exhaustion
Initial configuration: 20 max connections. Broke when 3rd clinic onboarded.
[databases]
vaidyaai = host=localhost port=5432 dbname=vaidyaai
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3
Result: Can now handle 1000 concurrent connections with only 25 actual database connections. Connection pooling solved it.
Problem 2: Slow Drug Interaction Queries
The nested subquery for checking drug interactions was taking 800-1200ms initially.
CREATE MATERIALIZED VIEW active_patient_medications AS
SELECT
patient_id,
clinic_id,
ARRAY_AGG(medicine_generic_name) as active_medicines
FROM prescriptions p
JOIN prescription_medicines pm ON p.id = pm.prescription_id
JOIN medicines m ON pm.medicine_id = m.id
WHERE p.status = 'active'
GROUP BY patient_id, clinic_id;
CREATE INDEX ON active_patient_medications (patient_id, clinic_id);
-- Refresh every hour
REFRESH MATERIALIZED VIEW CONCURRENTLY active_patient_medications;
Result: Query time dropped from 800ms to 42ms. 19Γ improvement.
Problem 3: Schema Migration Complexity
When we added a new column (allergies_verified), we had to run migration on all tenant schemas.
DO $$
DECLARE
schema_name text;
BEGIN
FOR schema_name IN
SELECT nspname FROM pg_namespace
WHERE nspname LIKE 'clinic_%'
LOOP
EXECUTE format('ALTER TABLE %I.patients ADD COLUMN allergies_verified BOOLEAN DEFAULT FALSE', schema_name);
END LOOP;
END $$;
Result: One script migrates all tenants. Takes ~5 seconds for 10 clinics.
Database Growth: 3 Months in Production
Why PostgreSQL Won (Beyond Performance)
The performance benchmarks clearly favor PostgreSQL, but there are other critical factors:
1. Community & Ecosystem
PostgreSQL:
- Better documentation (especially for advanced features)
- Active community (Stack Overflow, Reddit)
- Rich extension ecosystem (PostGIS for location data, pg_trgm for fuzzy search)
MySQL:
- Larger installed base (more tutorials)
- Better beginner resources
- More managed hosting options
2. License & Ownership
PostgreSQL: MIT-like license, truly open-source, community-governed
MySQL: Owned by Oracle. Open-source but with commercial implications (MariaDB fork exists for a reason)
3. Advanced Features We Actually Use
| Feature | PostgreSQL | MySQL | Impact for VaidyaAI |
|---|---|---|---|
| True schemas | β | β | Multi-tenant isolation |
| JSONB with indexing | β | β οΈ (limited) | Fast prescription search |
| Full-text search | β | β οΈ (basic) | Patient record search |
| Array data types | β | β | Medicine lists, allergies |
| Window functions | β Excellent | β Good | Analytics queries |
| Replication | β Built-in | β Built-in | High availability |
When MySQL Might Be Better
To be fair, there are scenarios where MySQL is the right choice:
1. Simple CRUD Application
If your healthcare app is just basic create-read-update-delete operations with no complex queries, MySQL's simplicity is an advantage.
2. Read-Heavy Workload with Replication
MySQL's replication is easier to set up for read replicas. If you're serving dashboards to 100+ users, MySQL's read replica setup is simpler.
3. Shared Hosting Environment
More shared hosts offer MySQL than PostgreSQL. If you're on a βΉ500/month hosting plan, MySQL might be your only option.
4. Team Familiarity
If your entire team knows MySQL inside-out but has never touched PostgreSQL, the learning curve cost might outweigh technical benefits.
"The best database is the one your team can operate confidently in production. A poorly-tuned PostgreSQL setup will perform worse than a well-tuned MySQL setup."
Migration Strategy (If You're Currently on MySQL)
If you're running healthcare SaaS on MySQL and want to migrate to PostgreSQL, here's the approach we'd take:
Step 1: Schema Mapping
MySQL and PostgreSQL have subtle differences in data types:
| MySQL Type | PostgreSQL Equivalent | Notes |
|---|---|---|
| INT AUTO_INCREMENT | SERIAL or BIGSERIAL | Sequence-based |
| DATETIME | TIMESTAMP | Timezone handling differs |
| TEXT | TEXT | Same (but PG has no size limit) |
| ENUM | CREATE TYPE AS ENUM | Separate type definition needed |
| JSON | JSONB | Use JSONB for better performance |
Step 2: Data Migration
Use pgloader for automated migration:
LOAD DATABASE
FROM mysql://user:pass@localhost/vaidyaai
INTO postgresql://user:pass@localhost/vaidyaai
WITH include drop, create tables, create indexes, reset sequences
SET maintenance_work_mem to '512MB', work_mem to '256MB';
Step 3: Parallel Running
Run both databases in parallel for 2-4 weeks:
- All writes go to both MySQL and PostgreSQL
- Reads come from MySQL (production)
- Compare query results between both databases
- Tune PostgreSQL performance to match/exceed MySQL
Step 4: Cut Over
Switch reads to PostgreSQL, monitor for 24 hours, then deprecate MySQL.
Cost Analysis: PostgreSQL vs MySQL
Both databases are open-source, but there are hidden costs:
Hidden costs to consider:
- Developer time: PostgreSQL has steeper learning curve (20-40 hours to master)
- Hosting: Slightly more expensive for managed PostgreSQL (βΉ500-1000/month difference)
- Tools: MySQL has more GUI tools (phpMyAdmin, MySQL Workbench)
- Migration: If switching from MySQL, budget 40-80 hours of engineering time
Our Recommendation
For healthcare SaaS specifically, choose PostgreSQL if:
- β You need multi-tenant isolation (schema-per-tenant)
- β You have complex queries (joins, subqueries, JSON operations)
- β You need ACID guarantees (prescriptions, billing)
- β You'll use advanced features (full-text search, JSONB, arrays)
- β Your team can invest 20-40 hours learning PostgreSQL
Choose MySQL if:
- β Simple CRUD operations only
- β Your team already knows MySQL extremely well
- β You're on shared hosting with no PostgreSQL option
- β You don't need advanced features
VaidyaAI's Decision
We chose PostgreSQL and haven't regretted it. The schema-per-tenant architecture gives us peace of mind for data isolation, and the performance benefits are real.
If I were starting today, I'd make the same choice.
Need Help Choosing a Database?
I consult with healthcare startups on technical architecture. 1-hour session: βΉ15,000
Book ConsultationConclusion: PostgreSQL Wins for Healthcare SaaS
After 3 months in production with 1,100+ prescriptions processed, the data is clear:
PostgreSQL outperforms MySQL on every metric that matters for healthcare SaaS:
- β 1.8Γ faster complex queries
- β 4.5Γ faster JSON operations
- β Better multi-tenant isolation
- β Stronger ACID guarantees
- β Richer feature set
The learning curve is worth it. The performance gains are real. The peace of mind from proper data isolation is priceless.
If you're building healthcare SaaS in 2026, start with PostgreSQL. Your future self (and your lawyers) will thank you.
See VaidyaAI's Architecture in Action
Book a 20-minute technical demo and see our PostgreSQL-powered multi-tenant architecture.
Schedule Demo