diff --git a/Plugins/org.secc.FamilyCheckin/Cache/CheckinCache.cs b/Plugins/org.secc.FamilyCheckin/Cache/CheckinCache.cs index 01ca8fc17..72003e9f5 100644 --- a/Plugins/org.secc.FamilyCheckin/Cache/CheckinCache.cs +++ b/Plugins/org.secc.FamilyCheckin/Cache/CheckinCache.cs @@ -176,8 +176,11 @@ private static List UpdateKeys( Func> keyFactory, string en var now = DateTime.UtcNow; var currentKeys = AllKeys(); - // If within throttle window and we have existing keys - if ( currentKeys.Any() && ( now - _lastKeysRefreshUtc ) < KeysRefreshInterval ) + // If within the throttle window, serve the cached key list (applying ensure/remove below). + // An empty list can be a valid answer (e.g. overnight, before the day's first check-in) and must + // still be throttled; otherwise every caller re-runs keyFactory(), which may be an expensive DB query + // (e.g., Attendance keyFactory can trigger a full Attendance scan that returns zero rows). + if ( ( now - _lastKeysRefreshUtc ) < KeysRefreshInterval ) { bool modified = false; diff --git a/Plugins/org.secc.FamilyCheckin/README.md b/Plugins/org.secc.FamilyCheckin/README.md index f172872d9..cb2fc1de4 100644 --- a/Plugins/org.secc.FamilyCheckin/README.md +++ b/Plugins/org.secc.FamilyCheckin/README.md @@ -127,6 +127,23 @@ Quartz `IJob`s (both `[DisallowConcurrentExecution]`); scheduled in Rock, not se |------------|---------| | `CheckinGroupFieldType` | Picks a single or (configurably) multiple check-in groups; stores `Group.Guid`. Backed by `CheckinGroupPicker` and `CheckinGroupFieldAttribute`. | +### Caching + +`CheckinCache` — subclassed by `AttendanceCache`, `OccurrenceCache`, and +`MobileCheckinRecordCache` — keeps a per-type **key list** in `RockCache` (region `AllItems`, key +`{TypeName}:All`) next to the cached entities, so `All()` / `AllKeys()` can enumerate without a DB +round trip. Each subclass supplies a `keyFactory` that rebuilds that list from the database. +`CheckinKioskTypeCache` is **not** part of this layer — it uses Rock's own `ModelCache`. + +**Key-list refresh throttling (PR #281):** key-list refreshes are throttled to once per 10 seconds, +per cache type, per web-farm node — **including when the cached list is empty**. An empty list is a +valid state (overnight, before the day's first check-in), and refreshing on every call re-ran +`keyFactory()` every time; for `AttendanceCache` that is a full `dbo.Attendance` scan returning zero +rows. Tradeoff: after a cache clear or flush during active check-in, `AllKeys`-derived views +(attendance / occupancy) can read empty for up to 10 seconds before self-healing. Accepted — the +`ensureKey` / `removeKey` paths still apply inside the throttle window, so individual check-ins are +not lost. + ## Dependencies & Integrations - **Rock:** check-in engine (`CheckInState`, `CheckInActionComponent`), workflow engine,