Tracking Member Activity — Data-Minimal and GDPR-Compliant
Photo: Markus Spiske · Rawpixel (CC0 1.0), via Openverse
Almost every platform eventually wants to know who is actually active. A studio wants to see which members are still engaged and which are slowly drifting away. A trainer wants to know whether their clients even open the app. And we, as the operator, want to understand whether the product is being used or just sitting idle. The temptation is great to simply record everything for this purpose — every click, every page, every movement — and worry about the analysis later.
For a health platform, that is exactly the wrong answer. The more fine-grained behavioral data you hoard, the greater the risk you carry around with you. This week I describe how we capture member activity without building a detailed movement profile of every person — and why the principle "as much activity signal as necessary, as little stored data as possible" has guided every design decision.
Why Not Just Log Everything
A complete usage log sounds tempting because it could answer any future question. The price for that, however, is high. Anyone who records when someone opened which page is building a movement profile — and on a platform tied to blood glucose readings, nutrition plans, and training data, such a profile is itself something that needs protecting. It becomes a target, a subject-access obligation, and a deletion obligation.
The GDPR isn't a bureaucratic nuisance here, but the right way of thinking about the problem. Data minimization means: I first ask myself which question I actually need to answer, and then store only what that question genuinely requires. Our question is almost never "What did person X do, and when?" but "Is person X still active?" That's a much smaller signal — and the entire model is built around it.
An Append-Only Event Stream
At the core sits a single construct: the UserActivityEvent. We capture activity as an append-only stream. Events are appended, never overwritten, and never rewritten after the fact. That has two advantages. First, pure appending is technically robust — there are no competing updates to the same record, no locks, no lost writes under load. Second, an append-only log is easy to reason about: whatever is in it happened, and whatever is no longer needed can later be removed wholesale.
An event is deliberately sparse in content. It records that a user did something at a given moment, but not the details of what happened on which screen. We write these events where they naturally arise — at the system's natural transition points, which our <Domain>.<Process>.<Layer> logging convention already marks. That way the activity signal stays a by-product of normal usage rather than a second, parallel surveillance system.
The Hybrid 30-Day Active Predicate
The real intelligence lies not in storing but in evaluating. Who counts as active is decided by a hybrid 30-day active predicate. "Hybrid" means the answer comes from more than one signal source: the recent event stream for the last few days, and condensed metrics for anything further back. That way we can say whether someone was active within the last 30 days without having to permanently retain every single interaction from those 30 days.
The trick lies precisely in this separation. Fine-grained events are only useful for as long as they're fresh — for questions about the last few days. For the longer-term statement "active or not," a condensed form is enough. This decouples the usefulness of the signal from its shelf life: the predicate stays meaningful while the raw data behind it is allowed to age and disappear. That's exactly why the window is fixed at 30 days rather than left open — long enough to capture habits, short enough to never become an archive.
Photo: Rawpixel (CC0 1.0), via Openverse
Presence Without Location Tracking
Closely related to activity is presence — the question of who is online right now. That's what GetOnlineUserIds is for. This method makes presence visible without drawing on location or content data. It answers exclusively which accounts showed a sign of life within a short time window, and simply returns a set of user IDs for that.
That's a deliberately narrow definition. For us, "online" doesn't mean "is at location Y" or "is currently viewing content Z," only "was reachable a moment ago." For a little presence indicator next to a member, or for showing that a trainer is currently available, that's entirely sufficient — and at this point, you don't actually want to know more than that. Anything beyond this yes/no statement would be a step back toward a movement profile.
Backfill, Prune, and the GDPR Sweep
A model on a greenfield is one thing; a model in live operation is another. Three background processes make sure the activity data stays healthy over time.
- Backfill filled in the history once. Without it, the active predicate would have behaved at launch as if nobody were active, simply because there were no past events yet. Backfill reconstructed a plausible starting point from traces already present in the system.
- Prune is the ongoing operation: a job regularly removes old events once they've served their purpose for the most recent evaluation. So the append-only stream doesn't grow without bound — it's continuously trimmed at the back end.
- The GDPR sweep, finally, enforces data-minimal retention as a hard rule. It makes sure events don't linger longer than they're needed, and that deletion doesn't depend on a manual step but happens automatically.
These three work together. Backfill makes the predicate usable from day one, Prune keeps the data volume small, and the Sweep makes data minimization mandatory rather than optional. They run in the background as workers and, like our other workers, can be paused via the runtime feature flags if we ever need to halt them.
What This Means in Practice
For a studio or a trainer, little changes visibly on the surface, and that's intentional. You see who is active and who is online right now — exactly the two statements that matter for day-to-day work. What you don't see, because it doesn't exist, is a gapless chronicle of everything a person did in the app.
For us as the operator, the payoff is a quieter conscience and a smaller risk. A subject-access request can be answered without wading through a mountain of behavioral data, and deletion is already built into the system anyway. The principle of keeping as much activity signal as necessary and as little stored data as possible is therefore not a slogan, but the actual shape the data model has taken.
What's Next?
Next week: a cross-industry calendar that brings bookings, personal-training sessions, and club events together in a single view.
Comments
No comments yet. Be the first to share your thoughts!
Leave a Comment