Database Index Choices vs Throughput
Observations/Conclusions
There are some hard trade-offs here: Queue Performance, Delete Performance, Processing Performance, and Query Performance. According to the numbers, it appears a cluster key of ExternalDeviceId, CreatedDateTime, MessageId (Test 5) is the best choice, but not by a significant amount. Further testing will be required to see if this choice performs well enough when Querying for messages, since it has no other indexes and requires a clustered indexed range scan for every query.
Clustering by ExternalDeviceId, CreatedDateTime, MessageId results in the smallest database size of all the options because it has the fewest indexes.
We expected having a separate QueuedMessages table (to store new messages entering the system) would provided the best solution with respect to table locking during database maintenance (index rebuilds, etc), however; we did not see much improvement in this regard. Further testing on this solution should be investigated when we have more time.
The database log file was by far the hardest hit (by a factor of 3-4) from the 3 database files that make up the Queue database. We recommend separating the 3 database files on separate drives and IO channels with the LOG file being on the fastest disk. Additionally, backups should be performed to a separate disk so there is no disk contention during the backup.
There is a huge advantages from a scalability standpoint to having the database run on SSDs which is likely not possible running on vms in the data center. The machine running the database on SSDs exhibited none of the issues seen on the disk based solution with respect to timeouts during database maintenance.
The biggest outstanding risk is how the database performance over time with 100s of million records. On the disk based system we see dramatic performance degradation during database maintenance activities such as backup and index maintenance. The concern here is that if HL7 messages cannot be stored in a timely fashion that HL7 interfaces will timeout.
Test Scenario
Chicago Message set x 2 streams = 200K messages
ThroughputTest.cxn (attached) Database Index Choices vs Throughput
Pause all queues except the Branch Channel measure total time to Queue all messages (Queue Time)
Un-Pause all channels. Measure total time to Process all Messages (Processing Time)
Execute the delete command below, but WITHOUT the DROPCLEANBUFFERS call.
Query for Patient with ID = 105160 from UI
Execute the delete command below, but WITH the DROPCLEANBUFFERS call.
Query for Patient with ID = 105160 from UI
Clear Queues after each test using clear command below
Execute a database backup from the Client UI to clear log, etc
Test # | Rev | Computer | Clustered | Other | HL7Spy Send | Queue Time | Processing Time | Delete #5 | Query #6 | Delete #7 | Query #8 | Notes |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1849c28762f0 | JR | MessageId | One for each fields in Queue worklist | 138 | 362 | 411 | 340 | 88 | 13759 | 995 | Baseline database |
0 | 1849c28762f0 | NJ | MessageId | One for each fields in Queue worklist | 62 | 179 | 215 | 9 | 37 | 501 | 69 | Baseline database |
1 | 527c363e32dc | JR | ExternalDeviceId, CreatedDateTime | Filtered index on Queued messages | 136 | 278 | 434 | 188 | 81 | 11280 | 1271 | Use CreatedDateTime in queries instead of ModifiedDateTime |
1 | 527c363e32dc | NJ | ExternalDeviceId, CreatedDateTime | Filtered index on Queued messages | 60 | 146 | 215 | 93 | 28 | 287 | 53 | Use CreatedDateTime in queries instead of ModifiedDateTime |
2 | 08df4637d36e | JR | MessageId | Filtered index on Queued messages Fully covered index | 132 | 424 | 488 | 188 | 78 | 3143 | 1799 |
|
2 | 08df4637d36e | NICK | MessageId | Filtered index on Queued messages Fully covered index | 66 | 154 | 224 | 112 | 59 | 485 | 107 |
|
3 | 2854c3dfd94d | JR | NO Clustered index | Filtered index on Queued messages Fully covered index | 119 | 304 | 395 | 338 | 322 | 10632 | 2111 |
|
4 | 937eae4ff5ac | JR | MessageId | Filtered index on Queued messages Fully covered index | 118 | 283 | 380 | 217 | 71 | 8358 | 803 |
|
4 | 937eae4ff5ac | NJ | MessageId | Filtered index on Queued messages Fully covered index | 60 | 144 | 247 | 237 | 64 | 273 | 131 |
|
5 | 3261f232cd21 | JR | ExternaDeviceId, ModifiedDateTime, MesssageId | No CorrelationKey | 120 | 230 | 403 | 80 | 75 | 1065 | 1665 |
|
5 | 3261f232cd21 | NJ | ExternaDeviceId, ModifiedDateTime, MesssageId | No CorrelationKey | 60 | 124 | 247 | 210 | 83 | 82 | 134 |
|
6 | 3261f232cd21 | JR | MessageId | One fat index with everything | 117 | 225 | 375 | 170 | 166 | 1457 | 2840 |
|
6 | 3261f232cd21 | NJ | MessageId | One fat index with everything | 60 | 122 | 226 | 75 | 62 | 307 | 116 |
|
7 | 4d923df7e6da | JR | ExternalDeviceId, | No query indexes | 115 | 219 | 401 | 125 | 147 | 988 | 1206 |
|
7 | 4d923df7e6da | NJ | ExternalDeviceId, | No query indexes | 61 | 126 | 228 | 74 | 147 | 134 | 46 |
|
8 | 8f4a708a9733 |
| Separate QueuedMessage table | No query indexes | 115 | 180 | 435 | 130 | 211 | 1652 | 2583 |
|
9 | 5124566bec79 |
| Separate QueuedMessage table. MessageData and QueueData in same File Group | No query indexes | 118 | 192 | 445 | 127 | 93 | 1582 | 2945 |
|
* Calculated from:
select DATEDIFF(ss,(select top 1 ModifiedDateTime from Message order by MessageId)
,(select top 1 ModifiedDateTime from Message order by MessageId desc))
** Calculated from the following where {0} = the ExternalDeviceId of one of the non-branch devices found in SELECT * FROM MessageStatistic
DECLARE @nonBranchDeviceId int
set @nonBranchDeviceId = {0}
select DATEDIFF(ss,(select top 1 ModifiedDateTime from Message where ExternalDeviceId = 2 order by MessageId)
,(select top 1 ModifiedDateTime from Message where ExternalDeviceId = 2 order by MessageId desc))
** Command for Delete:
DBCC DROPCLEANBUFFERS
SET STATISTICS IO ONDECLARE @diff int,
@startDate DateTime,
@olderThan DateTime,
@timer DateTime,
@nonBranchDeviceId intset @nonBranchDeviceId = {0}
select top 1 @startDate = CreatedDateTime from Message where ExternalDeviceId=@nonBranchDeviceId order by MessageId
select @diff = DATEDIFF(SECOND, @startDate, (select top 1 ModifiedDateTime from Message where ExternalDeviceId=@nonBranchDeviceId order by MessageId desc))set @olderThan = DATEADD(SECOND, @diff/10, @startDate)
set @timer = SYSDATETIME()
delete TOP(5000) from Message where ExternalDeviceId = @nonBranchDeviceId AND QueueTypeCode != 'Q' AND ModifiedDateTime < @olderThan
PRINT 'Delete Time: ' + CAST(DATEDIFF(millisecond,@timer,SYSDATETIME()) as varchar(255))
*** Query command
DECLARE @timer DATETIME,
@nonBranchDeviceId intset @timer = SYSDATETIME()
set @nonBranchDeviceId = {0}SELECT TOP(100) * from Message
where ExternalDeviceId = @nonBranchDeviceId and PatientId = '105160' order by CreatedDateTime desc, MessageId desc
PRINT 'Query Time: ' + CAST(DATEDIFF(millisecond,@timer,SYSDATETIME()) as varchar(255))
*** Clear command
IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_Message_MessageData]') AND parent_object_id = OBJECT_ID(N'[dbo].[Message]'))
ALTER TABLE [dbo].[Message] DROP CONSTRAINT [FK_Message_MessageData]TRUNCATE TABLE Message
TRUNCATE TABLE MessageData
TRUNCATE TABLE ProcessingHistory
TRUNCATE TABLE MessageStatistic
TRUNCATE TABLE MessageStatisticHistory
CHECKPOINT
Test number | Query type | Message Statistics |
|---|---|---|
0 | DELETE | Table 'Message'. Scan count 1, logical reads 144794, physical reads 164, read-ahead reads 2905, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
0 | QUERY | Table 'Message'. Scan count 1, logical reads 1724, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
1 | DELETE | Table 'Message'. Scan count 1, logical reads 81811, physical reads 3, read-ahead reads 2548, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
1 | QUERY | Table 'Message'. Scan count 1, logical reads 629, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
1 NJ | DELETE | Table 'Message'. Scan count 1, logical reads 61359, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
2 | DELETE | Table 'Message'. Scan count 1, logical reads 46063, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
2 | QUERY | Table 'Message'. Scan count 1, logical reads 814, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. |
2 NJ | DELETE |