-
Notifications
You must be signed in to change notification settings - Fork 14
Performance: Fundamentals
This document outlines some basic information that is relevant for understanding the performance of CommCare applications and where different performance can be expected in the platform.
CommCare is designed to run on resource limited java platforms. While we no longer consider extremely limited java platforms (1MB of memory or less) to be in scope, the code and system are designed to keep very little in working memory.
CommCare's storage is encrypted at rest. This adds a meaningful (but not necessarily overly meaningful) hit to any I/O performed.
In order to fuel rapid development and consistency CommCare is heavy on abstractions which add some amount of overhead on their own to any "medium->large" N evaluations which occur using XPath models.
CommCare is a flexible platform, and is built with the assumption that data model flexibility is more relevant than the advantages that come with highly tuned, consistent models. As such, it is useful to know that the developers consider there to be points at which CommCare's abstraction layer will "max out" for a specific project's needs. Beyond that point, projects will need to use a mix of models (like using Case Claim and synchronous requests to isolate and shard the dataset) to achieve performant results.
In general CommCare should be quite fast on most platforms for aggregate data models up to the low-thousands. Aggregate models here refers to how many models need to be considered in a scope, so even if there are only 50 patients being queried, if the query also needs to consider, say, a list of up to 10 'evaluation' cases for each patient, that query is actually a 500 case query scope.
Performance in the mid-range of queries (between low thousands and into the ten-thousands)
Performance expectations for "tenable" sizes of CommCare data before should be generally considered to start maxing out around the 100k level when all optimization/performance improvements align perfectly. This is also, in our experience, the level at which current mobile devices can expect to stop being meaningfully/usefully performant regardless of CommCare's query/optimization capacity (even with maximally optimizing write time/efficiency/memory, writing 100k records to encrypted storage on a mobile phone takes a massive amount of time), so we don't expect that limit to change meaningfully.
Note that many applications would be non-performant significantly before the 100k (or potentially even 10k) level due to query structure/design, so that's not an average case upper bound, but a best-case one.
A few aspects of how CommCare performs operations are useful to consider as the most relevant units of consideration when building a mental model of "what CommCare needs to do" to perform requested tasks.
CommCare hosts most of its internal database records as rows with a set of column-level indexed metadata along with a "blob" of bytes that represent the full record.
A case for instance, is stored as
|id| case_id | case_type | status | blob |
| 0| 232mroi4r23 | patient | open | [binary data] |
| 1| l2k3n42i323 | visit | open | [binary data] |
When running "indexed" operations like
count(instance('casedb')/casedb/case[@status = 'open'])
The case data is irrelevant. when running an operation which requires data internal to the case, like
count(instance('casedb')/casedb/case[@status = 'open'][inside_the_blob = 'match'])
CommCare has to perform a query to match and read each case 'blob' from storage, "deserialize" the blob into an internal object, and then "inflate" the object into an XML Tree for reading.
CommCare utilizes different strategies to improve performance for each of those three steps, and some optimizations may be able to optimize some of them but not others.
On most platforms CommCare runs backed by a SQL database.
Generally speaking the bottleneck on most operations will be the number of requests that need to be made to a database in order to complete that operation.
Some operations on mobile devices are processor bound in ways that may be unexpected to developers coming from server or desktop processing backgrounds.
String operations in CommCare can be a surprisingly large bottleneck to performance. Since strings and char spans in CommCare are streamed to/from storage quite regularly, internal JVM/Dalvik optimizations for strings can end up not triggering. CommCare makes a strong effort to ensure that data streamed from storage is interned whenever possible, but bulk operations requiring string manipulation (like join()
, or substring-before
) can present challenges.