| We're currently using the schema-per-tenant, and it's working very well for us: * No extra operational overhead, it's just one database * Allows to delete a single schema, useful for GDPR compliance * Allows to easily backup/restore a single schema * Easier to view and reason about the data from an admin point of view * An issue in a single tenant doesn't affect other tenants * Downtime for maintenance is shorter (e.g. database migration, non-concurrent REINDEX, VACUUM FULL, etc.) * Less chance of deadlocks, locking for updates, etc. * Allows easier testing and development by subsetting tenants data * Smaller indexes, more efficient joins, faster table scans, more optimal query plans, etc. With row level security, every index needs to be a compound index * Easy path to sharding per tenant if needed. Just move some schemas to a different DB * Allows to have shared data and per-tenant data on the same database. That doesn't work with the tenant-per-database approach There are a few cons, but they are pretty minor compared to the alternative approaches: * A bit more code to deal in the tenancy, migrations, etc. We opted to write our own code rather than use an existing solution * A bit more hassle when dealing with PostgreSQL extensions . It's best to install extensions into a separate extensions schema * Possible caching bugs so you need to namespace the cache, and clear the query cache when switching tenant * The security guarantees of per tenant solution aren't perfect, so you need to ensure you have no SQL injection vulnerabilities |
We ran into issues here and there but always found a way to work around them:
* Incremental backups were a pain because of needing to lock so many objects (# of schemas X # of tables per schema).
* The extra code to deal w/ migrations was kinda messy (as you mentioned).
* Globally unique IDs become the combination of the row ID + the tenant ID, etc...
For us though the real deal-breaker turned out to be that we wanted to have real foreign keys pointing to individual rows in tenant schemas from outside of the tenant schema and we couldn't. No way to fix that one since with multi-schema the "tenant" relies on DB metadata (the schema name).
We ended up migrating the whole app to RLS (which itself was a pretty interesting journey). We were afraid of performance issues since the multi-schema approach kinda gives you partitioning for free, but with the index usage on the RLS constraints we've had great performance (at least for our use case!).
After quite a bit of time working with both multi-schema & RLS I probably wouldn't go back to multi-schema unless I had a real compelling reason to do so due to the added complexity. I really liked the multi-schema approach, and I think most of the critiques of it I found were relatively easy to work around, but RLS has been a lot simpler for us.