From 07d68e5d0164ee0fbc74c73931a0c4aed0880c15 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Tue, 3 Nov 2026 15:21:39 +0100 Subject: [PATCH 1/2] Added docs about cf-reactor performance Signed-off-by: Victor Moene --- .../reference/components/cf-reactor.markdown | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/content/reference/components/cf-reactor.markdown b/content/reference/components/cf-reactor.markdown index 76d44f55b..cc749130a 100644 --- a/content/reference/components/cf-reactor.markdown +++ b/content/reference/components/cf-reactor.markdown @@ -94,6 +94,49 @@ The daemon is then signaled to handle the event and run the bundles of the `then The delay depends on factors such as the polling interval, policy reloads, and slow code execution. However, `cf-reactor` guarantees that changes will be detected as fast as possible and the corresponding code executed, provided that execution does not fail. +## Performance + +`cf-reactor` uses these threads: + +- A watcher thread checks for events, and queues the events it detects. +- The main thread takes the events from the queue and runs their [`then`][events#then] bundles, one event at a time. +- For `package_installed` and `package_uninstalled` events, a listing thread lists the installed packages every 10 seconds. + There is one listing thread per [package module][Package modules] (e.g. `apt_get`), shared by all the `events` promises using it, whatever the number of packages they watch. + The watcher thread compares these lists to detect the events. + +How many events per second it can handle depends on the event type and on how long the `then` bundles take to run. + +Each `events` promise fires at most once per check, so at most once per [`interval`][events#interval]. +With `N` `events` promises and an interval of `I` seconds, `cf-reactor` has at most `N / I` events per second to handle. +Changes happening between two checks are seen as one event. + +The figures below were measured on an Amazon EC2 `t2.micro` instance (1 vCPU, Intel Xeon E5-2686 v4 at 2.30 GHz, 1 GB of memory) running Ubuntu 24.04. +Each `events` promise watched its own file, user or package, with the default `interval` of 1 second, and its `then` bundle had a single `reports` promise. + +The watched files, users and packages were changed for 30 seconds, at a fixed number of events per second. +The test was repeated with more and more events per second, until `cf-reactor` could no longer handle the events as fast as they happened. +The limit is the most events per second `cf-reactor` can handle for each event type: + +| Event type | Limit (events/s) | +| -------------------------- | ---------------: | +| `file_deleted` | 138 | +| `file_created` | 133 | +| `file_modified` | 145 | +| `file_content_modified` | 21 | +| `file_permissions_changed` | 15 | +| `file_owner_changed` | 14 | +| `file_size_exceeds` | 25 | +| `user_added` | at least 3.8 | +| `user_removed` | at least 4.2 | +| `package_installed` | at least 1.2 | +| `package_uninstalled` | at least 1.2 | + +**Notes:** + +- Users and packages could not be added or removed fast enough to reach the limit of `cf-reactor`: these figures are lower bounds. +- Above the limit, `cf-reactor` doesn't lose events: they pile up in the queue and are handled late. +- Slower `then` bundles lower these limits. + ## Enterprise hub tasks On CFEngine Enterprise hubs, `cf-reactor` also listens to `NOTIFY` events in the PostgreSQL database and performs actions when those events occur, and runs periodic hub tasks: From abbe31b884af32df489372b49e14ec891eeaf9fc Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Mon, 9 Nov 2026 10:17:33 +0100 Subject: [PATCH 2/2] Made cf-reactor performance clearer by adding actual then bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarified what cf-reactor’s performance depends on: the speed of the event type and the speed of the then bundle Showed what then bundle was used for the experiments. Signed-off-by: Victor Moene --- .../reference/components/cf-reactor.markdown | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/content/reference/components/cf-reactor.markdown b/content/reference/components/cf-reactor.markdown index cc749130a..e25c5211e 100644 --- a/content/reference/components/cf-reactor.markdown +++ b/content/reference/components/cf-reactor.markdown @@ -104,14 +104,27 @@ The daemon is then signaled to handle the event and run the bundles of the `then There is one listing thread per [package module][Package modules] (e.g. `apt_get`), shared by all the `events` promises using it, whatever the number of packages they watch. The watcher thread compares these lists to detect the events. -How many events per second it can handle depends on the event type and on how long the `then` bundles take to run. +How many events per second it can handle depends on two things: + +- The cost of `cf-reactor` itself: checking for the event, which depends on the event type, and setting up and running the `then` bundles. +- The work done by the `then` bundles, and anything outside of `cf-reactor`, such as how fast the package manager lists the installed packages. Each `events` promise fires at most once per check, so at most once per [`interval`][events#interval]. With `N` `events` promises and an interval of `I` seconds, `cf-reactor` has at most `N / I` events per second to handle. Changes happening between two checks are seen as one event. The figures below were measured on an Amazon EC2 `t2.micro` instance (1 vCPU, Intel Xeon E5-2686 v4 at 2.30 GHz, 1 GB of memory) running Ubuntu 24.04. -Each `events` promise watched its own file, user or package, with the default `interval` of 1 second, and its `then` bundle had a single `reports` promise. +Each `events` promise watched its own file, user or package, with the default `interval` of 1 second, and ran this `then` bundle: + +```cf3 +bundle reactor record_event(file) +{ + reports: + "handled '$(file)'"; +} +``` + +Users were added and removed with `useradd` and `userdel`, and packages were installed and uninstalled with `dpkg` and watched with the `apt_get` package module. The watched files, users and packages were changed for 30 seconds, at a fixed number of events per second. The test was repeated with more and more events per second, until `cf-reactor` could no longer handle the events as fast as they happened. @@ -133,8 +146,10 @@ The limit is the most events per second `cf-reactor` can handle for each event t **Notes:** -- Users and packages could not be added or removed fast enough to reach the limit of `cf-reactor`: these figures are lower bounds. +- `useradd`, `userdel` and `dpkg` could not add, remove, install or uninstall users and packages fast enough to reach the limit of `cf-reactor`: these figures are lower bounds. + On a real system, users and packages rarely change faster than this. - Above the limit, `cf-reactor` doesn't lose events: they pile up in the queue and are handled late. + The queue has no size limit: if events keep coming faster than `cf-reactor` handles them, the delay keeps growing, and so does the memory used by the queue, until `cf-reactor` runs out of memory. - Slower `then` bundles lower these limits. ## Enterprise hub tasks