Sql-server – Performance tuning for multi-tenant database

database-designindexmulti-tenantperformancesql server

The database, MS SQL 2014 Standard Edition, is becoming a bottleneck in our multi-tenant web application. Aware that most performance issues can be solved by making code more efficient, however looking beyond that now.

When load is high, the database seems to grind to a halt. At seemingly random times, it takes a massive slowdown (thinking table locking when one tenant inserts a load of data). Other times, we see deadlocked transactions. In the latest slowdown, a big chunk was deleting single rows from this table with queries like DELETE FROM tableX WHERE ID = x, resulting in a lot of Transaction (Process ID 183) was deadlocked on lock resources with another process and has been chosen as the deadlock victim errors.

Which of the below strategies would you recommend, if any?

  1. Changing clustered indexes from the auto-incrementing int PK to
    being tenantID + id. (note all tables have tenantID column and
    already a non-clustered index on tenantID):

    This would be an attempt to stop page/table locks due to activities from one tenant from affecting all other tenants. This seems recommended practice for multi-tenant DBs. Otherwise if a tenant inserts 4,000 rows or similar this seems to lock the table.

  2. Changing from default
    isolation level to Read Committed Snapshot so writes don't block
    reads and vice versa

  3. Moving to faster physical disks – currently
    on a RAID 10 array of SSDs, looking at the Intel NVMe PCI-e SSDs

    We use SQL Mirroring with High Safety Mode as we cannot lose a single booking/row due to a hardware error. Was under the impression all writes are written to disk, and the transaction won't complete till written to disk on the mirror. Networking between the two servers is sub 1ms and no contention. There's plenty RAM in the machine (64GB and doubling to 128GB in a few days), but does host a second multi-tenant DB (~40GB) for a different product.

Best Answer

Not as a counter-point to anything stated in @sp_BlitzErik's answer, but to more fully address one specific question out of the several posed by the O.P.:

Changing clustered indexes from the auto-incrementing int PK to being tenantID + id. (note all tables have tenantID column and already a non-clustered index on tenantID)

Simply stated: Yes, yes, yes, and probably again yes :-). Absolutely cluster on TenantID (first) and then the ID / Identity column. Right now you have the clustered index on just the IDENTITY column, and that values gets copied into the non-clustered index on TenantID, in which case you have two indexes, and the Identity value is in both. Consider the following regarding the current setup:

  • It is taking up more disk space to have the Identity column duplicated
  • When you INSERT or DELETE rows, both indexes need to be modified to account for the new / removed rows. That increases the potential for getting deadlocks (and you mention in a comment on @sp_BlitzErik's answer that this is indeed happening).
  • If it is not obvious, maintaining two indexes takes more time and more transaction log space than maintaining just one.
  • Given that most queries should (hopefully!) have the TentantID in the WHERE clause, this forces most queries to use the non-clustered index instead of going straight to the clustered index. This is an unnecessary performance hit on most of your queries. Only back-end / maintenance queries (i.e. those that can operate across the Tenants) would use the Identity column only to find rows, and it is highly doubtful that those represent the majority of the queries being processed.

For more details as to why you should proceed with this change, and at least one problem you might run into and how you can get passed it, please see my answer to the following question, also here on DBA.StackExchange:

Composite Primary Key in multi-tenant SQL Server database