Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The logging component’s intended performance

EHDS Annex II 1.1 asks that the harmonised software components “achieve the performance intended by its manufacturer” and be suitable for their intended purpose in normal conditions of use (Regulation (EU) 2025/327, docs/law/eu/ehds/text.html). Annex III point 2 asks the technical documentation for the system in place to evaluate that performance. This page states the performance Cadasto B.V. intends for FerroEHR’s European logging software component, as claims a test can check, and the test that checks them. The Regulation names no method and no figure, so both are FerroEHR’s own design.

Warning

A met claim shows what the software did on the run that measured it. It does not show that the component meets Annex II, which no conformity assessment has examined.

The claims

The component is the access log the hazard log assesses: every access through the API is recorded, by ehds-logging and ihe-atna through ferroehr::system_log, in the local store, and retrieved through IHE ITI-81. The claims hold under the deployment posture the instructions for use set for real data: the access log on, the local store on, and [audit] fail_mode = "closed".

ClaimStated performanceHow it is checked
CompletenessEvery composition create and read has exactly one access record, and every AQL execution at least one (one per EHR it served).Measured on every run: the access records of the measured window are counted through ITI-81 per operation id and compared with the operations that ran.
Loss under outageNo access succeeds without its record. A record the queue or the local store cannot take refuses the request with 503, and a record owed to a FHIR Audit Record Repository that is down stays owed and is delivered when it returns.Deterministic tests, not a load measurement: app/ferroehr-rest/tests/it/audit_e2e.rs (fail_closed_returns_503_when_channel_full, fail_closed_503_carries_openehr_error_body_and_retry_after), app/ferroehr/tests/it/access_event_fail_closed.rs, and app/ferroehr/tests/it/audit_feed.rs (arr_outage_leaves_rows_pending_then_delivers_on_recovery).
Latency addedWith the access log on, the median p50 and p95 request latency are at most 125 % of those with it off.Measured: the same workload with the log off and on, on one machine.
Throughput keptWith the access log on, the median throughput is at least 80 % of that with it off.Measured, as above.
RetrievalA page of 50 access records retrieved through ITI-81 from the measured window takes at most a quarter of a second at p95 (MAX_RETRIEVAL_P95_MS in the verdict script).Measured: 50 paged retrievals after each run.

“Normal conditions of use” for these figures are the load of the release performance guard: a store seeded with 20 EHRs of 10 compositions each, then 2,000 operations over 8 concurrent workers, of every 20 six composition creates, eight composition reads and six AQL queries (tools/loadtest). The intended-purpose statement names no load, so this one is chosen to be the one every release is already measured under. The ratios compare two runs on one machine, so they hold across machines in a way an absolute latency would not; the retrieval figure is absolute and generous for that reason.

The test

scripts/perf/audit.sh starts the quickstart on fresh volumes, three times with the access log off and three times with it on and failing closed, alternating the order. The load test runs the workload against each; with the log on it waits for the asynchronous log to settle after the seed, opens the measured window, and after the measured phase counts the window’s records per operation id (the ITI-81 subtype and _count=0) until two readings a second apart agree, then times the paged retrievals. scripts/perf/audit-verdict.sh compares the medians with the claims above and exits non-zero when one is not met; its --self-test holds each budget to its edge.

The release pipeline runs it on the release candidate in its performance leg. A claim not met refuses the release, and the summary is attached to the release as ferroehr-<tag>-logging-performance.md, which is the latest result for that release. The weekly performance guard runs it on main.

To run it yourself against an image:

cargo build --release -p loadtest
FERROEHR_PORT=8080 bash scripts/perf/audit.sh ghcr.io/ferrohealth/ferroehr:<tag> \
  ghcr.io/ferrohealth/ferroehr-postgres:<tag> perf-audit

What it does not measure

  • Records over syslog or the FHIR feed are not timed: both send from the stored record, after the request, so they add no latency to it.
  • The store’s size over years is not loaded: the retrieval figure holds for the store a run leaves. ITI-81 answers through a token index, so a search reads an index rather than every record, but no run measures a store of millions of records.
  • Access made straight against the database, outside the server, is not recorded at all (hazard H1).