Performance and Caching
This page outlines technical considerations for setting up a performant FHIR repository.
Smile CDR uses an internal mechanism called the Search Coordinator, which executes database search queries in a background thread in order to minimize the amount of time that a client waits for search results to become available.
The following diagram shows the execution flow for a typical search.
When a search begins, a check is first made to determine whether or not the query has already recently occurred. If two search requests are made in close succession for the exact same search parameters, the results from the first search will be reused and returned for the second instead of executing the same search again. This can result in time savings as the query cache is very fast compared to executing searches against the database.
In addition, the query cache can prefetch more results than have been requested by the user. This is done in order to optimize page requests. For example, if a user requests all Patients named "smith", they may receive only the first 10 results on the single page. The Search Coordinator may continue fetching more than the first 10 results in the background. If the client attempts to load resources beyond the number that has been pre-fetched and stored in the Query Cache, the Search Coordinator will begin a new search in the database for further results.
Note that requests against the query cache will always respect security and authorization rules. If a particular search is not permitted for the calling user or if the search returns results that the calling user is not permitted to receive, the security manager will deny this request per normal behaviour.
If the Search Coordinator determines that there is no appropriate search result already in the cache, a new search is executed.
As soon as a single page of results is available (the size of a page depends on the client request and the server default; however, it is typically a small number such as 50 results), these results are returned to the client. The server will continue fetching subsequent pages in the background, and these will be added to the query cache.
The search result returned to the client is a FHIR Bundle resource containing the page of results as well as a link that can be used to fetch the next page of results. If the client chooses to fetch the next page, these results will be retrieved from the query cache.
If the Search Coordinator determines that search result already exists in the query cache (and it is not too stale to return), the existing results will be returned again.
The validity period for a search result is configurable (see below). By default a search result will remain eligible for reuse for 1 minute.
When receiving search results from the FHIR Endpoint, there are several hints that indicate that a cached search result was returned:
X-Cache
will be returned, containing the value HIT
and the identity of the FHIR Endpoint. For example:X-Cache: HIT from https://fhir.acme.org:8000
The returned Bundle
will have a lastUpdated
time corresponding to the timestamp of the original search.
The Bundle
will have an ID that corresponds to the cache entry ID. This is meaningless on its own but if two searches return a Bundle
with the same ID then you will know that they came from the same entry in the query cache.
Use of the query cache does potentially mean that slightly out-of-date results can be returned. For example, consider the following scenario where all of these steps happen in rapid succession:
Patient?name=smith
is performed.Patient?name=smith
is performed.If the cache is set to 1 minute, and the above steps happen in less than 1 minute, the newly created patient from step 2 will not be reflected in the step 3 search results because the step 1 search results will be reused.
This is not an issue in most real-world scenarios so the benefit in performance is generally worth it; however, this should be considered when designing an application. If you are building a workflow that requires a search to reflect resources that were only just created, consider disabling the cache either globally or for the given request.
On the server, it is possible to adjust the default timeout for the query cache. If the dao_config.reuse_cached_results_timeout_millis
property is set to 0
(zero), the query cache will never be used to respond to a new search. Note that this does not mean that nothing will be stored in the query cache, as subsequent page requests for a single search will still use the query cache.
If the dao_config.reuse_cached_results_timeout_millis
property is set to a positive value, a search result in the query cache is eligible to be reused for the given number of milliseconds.
The client may request that a specific request skips the query cache by using the Cache-Control
header.
By using this header with a value of no-cache
, the client instructs the server that the query cache should not be considered.
For example, the following request searches for patients named "Smith" and will not use the query cache:
GET /Patient?name=smith
Cache-Control: no-cache
Accept: application/fhir+json
In some circumstances, the client may want to perform a search that should return as quickly as possible and that does not require a large number of results.
This can be accomplished by using a no-store
query, which avoids persisting search results in the query cache. This has several implications:
The following example shows a client request that can only return up to 50 results (any matches beyond 50 are ignored), and should return as quickly as possible.
GET /Patient?name=smith
Cache-Control: no-cache, no-store, max-results=50
Accept: application/fhir+json
When performing a FHIR search, one of the elements returned in the response Bundle is the Bundle.total
element, which contains a count of how many resources match the given search.
If you are building a system which relies on this total, there are several points to consider:
When performing a search that matches a large number of results, the first page of results may not have the Bundle.total
element populated. This is because the Search Controller will attempt to return data as soon as it becomes available, even if the total number of matching results is not known. Put another way, by default the total number of search results is not always included in responses in order to improve performance. This behavior can be adjusted as shown below.
If you are performing a search that requires only the count and does not actually require the corresponding data, you can avoid costly data loading and force a count to be loaded using the _summary
parameter. The following example shows a query which counts all Patients born on/after January 01 1980.
http://hapi.fhir.org/baseR4/Patient
This will return a result similar to the following:
{
"resourceType": "Bundle",
"id": "72282727-65f1-4f2f-8bb3-aa8721a6f729",
"total": 29
}
You can force a total count along with the accompanying data by using the _total
parameter. This parameter can have any of the following values:
For example:
http://hapi.fhir.org/baseR4/Patient