Skip to content

CQL Performance

TL;DR

For the CQL queries analyzed here, the relative performance of query evaluation in patients/s stays the same for datasets with 1 million patients compared to datasets with 100 thousand patients if the system resources are sufficient and Blaze performs equally or better for datasets with more patient history. In all cases the performance increases with rising system resources.

Systems

The following systems with rising resources were used for performance evaluation:

SystemProviderCPUCoresRAMSSDHeap Mem ¹Block Cache ²Resource Cache ³
LEA25on-premEPYC 7543P432 GiB2 TB8 GiB8 GiB2.5 M
LEA36on-premEPYC 7543P864 GiB2 TB16 GiB16 GiB5 M
LEA47on-premEPYC 7543P16128 GiB2 TB32 GiB32 GiB10 M
LEA58on-premEPYC 7543P32256 GiB2 TB64 GiB64 GiB20 M
A5N46on-premRyzen 9900X2496 GiB2 TB24 GiB24 GiB10 M

¹ Size of the Java Heap (JAVA_TOOL_OPTIONS) ² Size of the block cache (DB_BLOCK_CACHE_SIZE) ³ Size of the resource cache (DB_RESOURCE_CACHE_SIZE)

All systems have in common that the heap mem and the block cache both use 1/4 of the total available memory each. So the Blaze process itself will only use about half the system memory available. The rest of the system memory will be used as file system cache.

Datasets

The following datasets were used:

DatasetHistory# Pat. ¹# Res. ²# Obs. ³SSD Size
100k10 years100 k104 M59 M202 GiB
100k-fhfull100 k317 M191 M323 GiB
1M10 years1 M1044 M593 M1045 GiB

¹ Number of Patients, ² Total Number of Resources, ³ Number of Observations

The creation of the datasets is described in the Synthea section. The disc size is measured after full manual compaction of the database. The actual disc size will be up to 50% higher, depending on the state of compaction which happens regularly in the background.

Methods

Metric

The metric analyzed here are the number of patients a system can process per second. It was chosen because the CQL evaluation performance depends heavily on the number of patients available in Blaze. The datasets contain either 100 k or 1 million patients in order to represent two relevant sizes from where an interpolation or extrapolation towards the target size should be possible. The metric patients per second itself is independent from the actual number of patients and can therefore be used to compare the two population sizes analysed here.

With a given patients per second value, its always possible to calculate the to be expected CQL evaluation duration by dividing the target systems number of patients by that number. So for example, if the metric is 100 k patients/s Blaze will need 1 second if it contains 100 k patients and 5 seconds if it contains 500 k patients.

Measurement

Measurements are taken by using blazectl's evaluate-measure subcommand. That subcommand first creates both a Measure and a Library resource and evaluates the Measure afterwards. By creating resources on every run, there is a slight performance impact due to the way LSM trees used by RocksDB work. The measure evaluation returns a MeasureReport that contains the evaluation duration in an extension which ensures that external timing effects play no role.

Before each measurement Blaze is restarted. After that multiple runs are taken. The first run is used to obtain the number of hits and print the Bloom filter usage. It's duration isn't used. After this run the script waits for 10 seconds in order to give Blaze time to settle. During this time the Java just-in-time compiler and garbage collector and to some work. After that 9 runs are done were the duration for the first two runs are discarded. From the remaining 7 durations, the average and standard deviation are calculated. From the average and the total number of patients the final metric patients per second is calculated.

Systems

All measurements are taken on the same VM that has the size of LEA58. The OS is Ubuntu 22.04.4 LTS. Docker version is 26.1.4.

In this section, CQL queries for selecting patients which have observations with a certain code are analyzed. The codes were chosen to produce a wide range of hits (number of matching patients). For the 100k dataset the hits are 2 %, 60 % and 100 % of the total number of patients, for the 100k-fh dataset the hits are 2 %, 57 % and 100 % and for the 1M dataset the hits are 2.5 %, 60 % and 100 %.

The first chart shows the results for the 100k dataset. It shows that the performance raises with the system size and declines a lot with the number of patients found (hits). This decline can be explained because the Bloom filters are most effective for small number of hits.

The second bar chart shows the results for the 100k-fh dataset which differs by the 100 k dataset in that it contains a full history of patient data instead of a history capped at 10 years. Especially the number of observations is 191 M compared to only 59 M in the 100k dataset. Comparing the two bar charts, the performance is nearly identical. So for simple code search, the performance doesn't depend on the amount of patient history.

The third bar chart shows the results for the 1M dataset. For the two bigger systems LEA47 and LEA58, the relative performance measured in patients per second is identical to the performance Blaze shows at the smaller datasets with only 100 k patients. However the same can't be said for the two smaller systems LEA25 and LEA36, were the relative performance suffers due to memory limitations of that systems.

Data

DatasetSystemCode# HitsTime (s)StdDevPat./s
100kLEA2517861-62 k0.060.0031.540 M
100kLEA258310-560 k0.250.014402.3 k
100kLEA2572514-3100 k0.330.009300.3 k
100kLEA3617861-62 k0.050.0021.972 M
100kLEA368310-560 k0.120.005819.7 k
100kLEA3672514-3100 k0.180.004562.5 k
100kLEA4717861-62 k0.050.0011.975 M
100kLEA478310-560 k0.070.0021.363 M
100kLEA4772514-3100 k0.100.001963.7 k
100kLEA5817861-62 k0.050.0011.942 M
100kLEA588310-560 k0.070.0011.351 M
100kLEA5872514-3100 k0.090.0021.153 M
100k-fhLEA25788-02 k0.070.0031.513 M
100k-fhLEA2544261-657 k0.240.007425.2 k
100k-fhLEA2572514-3100 k0.350.007282.3 k
100k-fhLEA36788-02 k0.050.0021.913 M
100k-fhLEA3644261-657 k0.130.003756.3 k
100k-fhLEA3672514-3100 k0.210.006487.2 k
100k-fhLEA47788-02 k0.050.0021.981 M
100k-fhLEA4744261-657 k0.070.0011.370 M
100k-fhLEA4772514-3100 k0.100.001970.7 k
100k-fhLEA58788-02 k0.050.0021.979 M
100k-fhLEA5844261-657 k0.070.0021.367 M
100k-fhLEA5872514-3100 k0.090.0011.151 M
1MLEA2517861-625 k0.480.0252.079 M
1MLEA258310-5603 k9.330.033107.1 k
1MLEA2572514-3998 k14.040.08471.2 k
1MLEA3617861-625 k0.480.0032.081 M
1MLEA368310-5603 k4.740.038211.1 k
1MLEA3672514-3998 k7.150.032139.8 k
1MLEA4717861-625 k0.490.0122.054 M
1MLEA478310-5603 k0.670.0101.488 M
1MLEA4772514-3998 k1.000.010995.1 k
1MLEA5817861-625 k0.520.0121.930 M
1MLEA588310-5603 k0.660.0071.505 M
1MLEA5872514-3998 k0.780.0191.287 M
1MA5N4617861-625 k0.230.0094.353 M
1MA5N468310-5603 k0.380.0032.660 M
1MA5N4672514-3998 k1.610.006622.8 k

Example CQL Query

text
library "observation-17861-6"
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'

codesystem loinc: 'http://loinc.org'

context Patient

define InInitialPopulation:
  exists [Observation: Code '17861-6' from loinc]

The CQL queries can be executed with the following commands:

sh
cql/search.sh observation-17861-6
cql/search.sh observation-8310-5
cql/search.sh observation-72514-3
cql/search.sh observation-788-0
cql/search.sh observation-44261-6

In this section, CQL Queries for selecting patients which have observations with a certain code and value are analyzed. The values were chosen to produce a wide range of hits (number of matching patients). The hits are 10 %, 50 % and 100 %.

The first chart shows the results for the 100k dataset. It shows the number of patients a system can process per second as described above. The performance raises with system size. As with Simple Code Search, queries with a small number of hits are much faster as queries with a large number of hits due to Bloom filter optimizations.

The second chart shows the results for the 1M dataset. Here the performance of the 10 % hits query is identical to that of the the 100k dataset. That can be explained because Bloom filter don't need much memory. However the two smaller systems LEA25 and LEA36 show a degradation in the performance of the queries with larger number of hits. Here the memory limitations show while going into the actual database storage layer.

Data

DatasetSystemCodeValue# HitsTime (s)StdDevPat./s
100kLEA2529463-713.6 kg10 k0.280.010356.9 k
100kLEA2529463-775.3 kg50 k0.730.017137.7 k
100kLEA2529463-7185 kg100 k1.070.02993.7 k
100kLEA3629463-713.6 kg10 k0.120.008842.7 k
100kLEA3629463-775.3 kg50 k0.330.011306.0 k
100kLEA3629463-7185 kg100 k0.460.007219.4 k
100kLEA4729463-713.6 kg10 k0.070.0051.523 M
100kLEA4729463-775.3 kg50 k0.150.004676.3 k
100kLEA4729463-7185 kg100 k0.230.007440.1 k
100kLEA5829463-713.6 kg10 k0.060.0011.583 M
100kLEA5829463-775.3 kg50 k0.120.005869.4 k
100kLEA5829463-7185 kg100 k0.160.003609.1 k
1MLEA2529463-713.6 kg99 k2.720.024367.0 k
1MLEA2529463-775.3 kg500 k14.650.18768.2 k
1MLEA2529463-7185 kg998 k22.930.23543.6 k
1MLEA3629463-713.6 kg99 k1.170.017856.6 k
1MLEA3629463-775.3 kg500 k3.190.052313.6 k
1MLEA3629463-7185 kg998 k10.830.08492.3 k
1MLEA4729463-713.6 kg99 k0.600.0111.667 M
1MLEA4729463-775.3 kg500 k1.630.023612.6 k
1MLEA4729463-7185 kg998 k2.190.037456.8 k
1MLEA5829463-713.6 kg99 k0.590.0071.709 M
1MLEA5829463-775.3 kg500 k1.010.013993.3 k
1MLEA5829463-7185 kg998 k1.420.026702.4 k
1MA5N4629463-713.6 kg99 k0.280.0073.584 M
1MA5N4629463-775.3 kg500 k0.460.0322.160 M
1MA5N4629463-7185 kg998 k1.660.002601.8 k

CQL Query

text
library "observation-body-weight-50"
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'

codesystem loinc: 'http://loinc.org'
code "body-weight": '29463-7' from loinc

context Patient

define InInitialPopulation:
  exists [Observation: "body-weight"] O where O.value < 75.3 'kg'

The CQL query is executed with the following command:

sh
cql/search.sh observation-body-weight-10
cql/search.sh observation-body-weight-50
cql/search.sh observation-body-weight-100

In this section, CQL queries for selecting patients which have conditions with one of 10 codes are analyzed. The codes were chosen to produce both a low number and a high number of hits. For the 100k dataset the hits are 0.4 % and 95 %, for the 100k-fh dataset the hits are 2 % and 98 % and for the 1M dataset the hits are 0.4 % and 95 %.

The first chart shows the results for the 100k dataset. The performance raises with system size. As with Simple Code Search and Code Value Search, queries with a small number of hits are much faster as queries with a large number of hits due to Bloom filter optimizations.

The second chart shows the results for the 100k-fh dataset. For the 100k-fh dataset the performance of the query with small number of hits is lower because the number of hits if actually larger (2 %) as that of the 100k dataset with 0.4 %.

The third chart shows the results for the 1M dataset. As with the Code Value Search queries the performance for the query with low number of hits is the same across all system sizes, because Bloom filters don't need much memory. However the same can't be said for the query with 95 % hits. Here the LEA25 and LEA36 systems are clearly too small.

Data

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA253950.070.0041.524 M
100kLEA2595 k0.320.014311.8 k
100kLEA363950.060.0021.677 M
100kLEA3695 k0.180.005568.2 k
100kLEA473950.060.0011.772 M
100kLEA4795 k0.100.0011.005 M
100kLEA583950.060.0011.653 M
100kLEA5895 k0.090.0011.105 M
100k-fhLEA252 k0.150.008660.7 k
100k-fhLEA2598 k0.390.019259.2 k
100k-fhLEA362 k0.080.0011.184 M
100k-fhLEA3698 k0.180.004553.7 k
100k-fhLEA472 k0.060.0011.578 M
100k-fhLEA4798 k0.110.002949.3 k
100k-fhLEA582 k0.070.0011.536 M
100k-fhLEA5898 k0.090.0021.084 M
1MLEA254 k0.540.0411.845 M
1MLEA25954 k10.930.06891.5 k
1MLEA364 k0.570.0111.766 M
1MLEA36954 k5.650.033177.1 k
1MLEA474 k0.550.0051.813 M
1MLEA47954 k0.970.0161.029 M
1MLEA584 k0.560.0131.800 M
1MLEA58954 k0.840.0071.191 M
1MA5N464 k0.250.0104.001 M
1MA5N46954 k0.460.0022.181 M

CQL Query Frequent

text
library "condition-ten-frequent"
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'

codesystem sct: 'http://snomed.info/sct'

context Patient

define InInitialPopulation:
  exists [Condition: Code '444814009' from sct] or
  exists [Condition: Code '840544004' from sct] or
  exists [Condition: Code '840539006' from sct] or
  exists [Condition: Code '386661006' from sct] or
  exists [Condition: Code '195662009' from sct] or
  exists [Condition: Code '49727002' from sct] or
  exists [Condition: Code '10509002' from sct] or
  exists [Condition: Code '72892002' from sct] or
  exists [Condition: Code '36955009' from sct] or
  exists [Condition: Code '162864005' from sct]
sh
cql/search.sh condition-ten-frequent

CQL Query Rare

text
library "condition-ten-rare"
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'

codesystem sct: 'http://snomed.info/sct'

context Patient

define InInitialPopulation:
  exists [Condition: Code '62718007' from sct] or
  exists [Condition: Code '234466008' from sct] or
  exists [Condition: Code '288959006' from sct] or
  exists [Condition: Code '47505003' from sct] or
  exists [Condition: Code '698754002' from sct] or
  exists [Condition: Code '157265008' from sct] or
  exists [Condition: Code '15802004' from sct] or
  exists [Condition: Code '14760008' from sct] or
  exists [Condition: Code '36923009' from sct] or
  exists [Condition: Code '45816000' from sct]
sh
cql/search.sh condition-ten-rare

Data

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA2599 k0.350.013289.8 k
100kLEA3699 k0.190.003517.7 k
100kLEA4799 k0.110.001891.8 k
100kLEA5899 k0.100.0011.050 M
100k-fhLEA25100 k0.370.018273.0 k
100k-fhLEA36100 k0.200.006506.3 k
100k-fhLEA47100 k0.110.001870.3 k
100k-fhLEA58100 k0.100.0011.038 M
1MLEA25995 k11.780.06884.9 k
1MLEA36995 k5.960.015167.7 k
1MLEA47995 k1.050.008952.9 k
1MLEA58995 k0.860.0061.164 M
1MA5N46995 k0.480.0022.071 M

CQL Query

sh
cql/search.sh condition-all

Data

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA252 k0.590.017169.1 k
100kLEA362 k0.310.009326.7 k
100kLEA472 k0.170.004580.5 k
100kLEA582 k0.150.005683.1 k
100k-fhLEA252 k1.890.01652.8 k
100k-fhLEA362 k1.150.01486.9 k
100k-fhLEA472 k0.780.004127.9 k
100k-fhLEA582 k0.540.006184.4 k
1MLEA2516 k7.690.108130.0 k
1MLEA3616 k3.900.041256.7 k
1MLEA4716 k1.880.025531.2 k
1MLEA5816 k1.210.014823.8 k
1MA5N4616 k0.750.0581.333 M

CQL Query

sh
cql/search.sh inpatient-stress

Medication

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA259660.120.004806.4 k
100kLEA257 k0.120.006854.3 k
100kLEA369660.060.0031.684 M
100kLEA367 k0.060.0021.637 M
100kLEA479660.050.0001.930 M
100kLEA477 k0.050.0011.900 M
100kLEA589660.050.0011.889 M
100kLEA587 k0.060.0011.800 M
1MLEA2510 k1.390.005720.1 k
1MLEA2566 k1.310.011765.3 k
1MLEA3610 k0.690.0031.440 M
1MLEA3666 k0.680.0071.478 M
1MLEA4710 k0.470.0022.145 M
1MLEA4766 k0.450.0042.214 M
1MLEA5810 k0.480.0032.085 M
1MLEA5866 k0.470.0032.140 M

CQL Queries

sh
cql/search.sh medication-1
cql/search.sh medication-7

Medication Ten

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA2515 k0.190.009533.7 k
100kLEA3615 k0.100.0021.022 M
100kLEA4715 k0.070.0021.486 M
100kLEA5815 k0.070.0011.491 M
1MLEA25149 k2.830.026353.4 k
1MLEA36149 k1.390.004719.4 k
1MLEA47149 k0.720.0061.393 M
1MLEA58149 k0.640.0031.574 M

Condition Code Stratification

Data

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA585.2 M12.790.3257.8 k
1MLEA5852.3 M399.6411.9662.5 k
1MA5N4652.3 M372.401.3502.7 k

CQL Queries

sh
cql/search.sh stratifier-condition-code

Laboratory Observation Code Stratification

Data

DatasetSystem# HitsTime (s)StdDevPat./s
100kLEA5837.8 M280.403.0260
1MA5N46380.5 M2768.199.3100

CQL Queries

sh
cql/search.sh stratifier-observation-laboratory-code