We recently observed many ROWGROUP_FLUSH
deadlocks while doing concurrent inserts into CCIs. I’m not really a concurrency kind of guy but I figured that I should blog about this just so other people with the same problem can find some information about it.
Deadlock Reproduction
The schedulers of the involved sessions are important in some way, especially when going for a simple reproduction. It’s easiest to just make all new sessions go the same CPU:
ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU = 0;
Obviously you should never do that in production. After affinity has been addressed I recommend creating a nearly empty source table and a new CCI table:
DROP TABLE IF EXISTS dbo.CCI_DEADLOCKED; CREATE TABLE dbo.CCI_DEADLOCKED ( COL VARCHAR(1500), INDEX CCI CLUSTERED COLUMNSTORE ); CREATE TABLE ##SOURCE_IDS (ID BIGINT NOT NULL); INSERT INTO ##SOURCE_IDS WITH (TABLOCK) SELECT TOP (1048576) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM master..spt_values t1 CROSS JOIN master..spt_values t2 OPTION (MAXDOP 1);
One way to see the deadlock is to quickly kick off two inserts into the CCI_DEADLOCKED
table from different sessions. Inserting a larger amount of data means that you’ll have more time to kick off the second session before the first completes, but a longer rollback time on the first session. On my machine inserting 1048576 rows of VARCHAR(1500)
data seems like a reasonable compromise:
INSERT INTO dbo.CCI_DEADLOCKED SELECT REPLICATE('Z', 1500) FROM ##SOURCE_IDS OPTION (MAXDOP 1, MAX_GRANT_PERCENT = 0);
The second session waits on the first with a LCK_M_IX
wait event. The first session loads all of its rows into the delta store, then deadlocks and rolls them all back. You can see this happen in near real time by looking at sys.dm_db_column_store_row_group_physical_stats:
Here’s the deadlock XML for those who are interested in that kind of thing:
unknown
INSERT INTO dbo.CCI_DEADLOCKED SELECT REPLICATE('Z', 1500) FROM ##SOURCE_IDS OPTION (MAXDOP 1, MAX_GRANT_PERCENT = 0);
unknown
INSERT INTO dbo.CCI_DEADLOCKED SELECT REPLICATE('Z', 1500) FROM ##SOURCE_IDS OPTION (MAXDOP 1, MAX_GRANT_PERCENT = 0);
SSMS can't produce a deadlock graph for this type of deadlock. Below is the non-copy-and-pastable error message from it:
Failed to initialize deadlock control.
There is an error in XML document (1, 2497).
Instance validation error: 'ROWGROUP_FLUSH' is not a valid value for hobtlockSubresource.
Plan Explorer from SentryOne can help us:
If you're following along at home don't forget to reset your affinity to whatever you had it before. The most common option:
ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU = AUTO;
The Workarounds
We've only observed this deadlock with multiple concurrent sessions insert to the delta store for the same target CCI due to server memory pressure or very low cardinality estimates (less than 251 rows). The correct mitigation depends on why you're seeing the issue in the first place. If you're seeing it due to low cardinality estimates then fix your estimates, or at the very least get them above 250 rows. If you're seeing them because the memory grant for the CCI build times out after 25 seconds then lower concurrency or increase server memory.
The problem can also be avoided by not doing concurrent inserts in the first place. In some cases a parallel insert may be a reasonable alterative. There's also some evidence that the deadlock is only seen when the number of rows for insert is very close to 1048576, but we weren't able to make any definitive conclusions around that.
Final Thoughts
Don't despair if you run into a ROWGROUP_FLUSH deadlock! There's probably something you can do in the application to avoid it. If you feel that you shouldn't have to take such measures feel free to vote for my connect item here.
Going Further
If this is the kind of SQL Server stuff you love learning about, you'll love my training. I'm offering a 75% discount to my blog readers if you click from here. I'm also available for consulting if you just don't have time for that and need to solve performance problems quickly.