Most CRM analytics precompute overnight and call the wait good enough. GuestMaker's contact analytics run live instead, and says plainly which part still isn't fast.

A marketing manager opens Contacts, clicks into Analytics, and reads "12 new contacts today." It is four in the afternoon. Forty guests have actually signed up since midnight. The dashboard isn't broken. It's working exactly as designed, and that's the quiet problem with an entire category of analytics products.
That design is the overnight snapshot: a job runs once a day, summarizes the database, and writes the results to a table built for fast reading. Every dashboard load after that is just reading a pre-baked answer instead of asking the real question. It's a completely reasonable instinct. A contact database with a couple of million rows is genuinely expensive to summarize from scratch on every click, and nobody wants a single page view to trigger a query that scans the whole thing.
The cost of that instinct is easy to miss because it never announces itself. The number on screen is confident. It has commas in the right places and a chart under it. Nothing about it looks stale. But it's a photograph, not a window, and the gap between the photograph and reality can run to a full day, for every customer, regardless of whether anyone happens to be looking, regardless of whether anything even changed. Staleness stops being an edge case and becomes the normal condition of the product.
This isn't a hypothetical rival's mistake. It's the first version we shipped for GuestMaker's own Contacts Analytics. A nightly workflow computed a snapshot, wrote it to a dedicated table, and every tenant's dashboard read from that table. It worked. It also meant every hotel group using it was looking at numbers that were, by construction, up to 24 hours behind, all day, as the normal case rather than the worst case.
We replaced it the same day we saw what that actually meant in practice, with an architecture that queries live, against the real database, on every load.
Contacts Analytics is a seven-tab suite sitting over a hotel group's whole guest database: Overview, Reachability and Consent, Database Health, Lifecycle and Bookings, Demographics and Language, Sources and Acquisition, and a Per-Hotel comparison table for groups running dozens of properties under one brand. The Lifecycle and Bookings tab draws on the same guest-stage logic that lets the AI concierge tell whether a guest has actually arrived. Each tab answers a genuinely different operational question, from "how many of our guests can we actually reach by email or WhatsApp" to "which of our properties is growing and which one is quietly shrinking."
The detail that makes "live" more than a marketing word is what happens when you click a number. Every clickable stat on every tab drops you straight into the filtered contact list behind it, the exact rows that made up that figure, the same drill-down we walk through tab by tab in what each of the seven tabs actually answers. That only works, honestly, if the number came from the real table a moment ago. A snapshot and a contact list can drift apart the instant a guest unsubscribes or a new booking lands, and a deep link built on stale data eventually lands you on the wrong guests, or none at all. Live analytics and a trustworthy drill-down are the same decision, not two separate features.
None of this is worth much as a claim unless it holds up against a real, large database, so it was tested against one at genuine scale, deep into the millions of guest records spread across dozens of individual properties. Most of the seven tabs return fast enough on that dataset that they need no caching at all. They just run.
Two views don't. A per-property rollup across dozens of hotels, and a figure showing how many contacts have an actual reservation attached to them, both take meaningfully longer than everything else on the page, by a wide enough margin that serving either one live on every single click, for every viewer, would be irresponsible.
The per-property rollup is really the same guest-matching question that a customer data platform for hotel groups exists to answer, just asked in aggregate rather than guest by guest. Several different ways of writing that second query were tried before accepting that the slowness wasn't a query-tuning problem. It's structural.
Here's the honest part, and it's worth naming directly rather than around: the query connecting a hotel group's contacts to their actual bookings has to reach into a table that records reservation guests platform-wide, and that table doesn't carry a per-customer marker the way most of the schema does. Answering "how many of this hotel group's contacts have ever booked" means touching a meaningful slice of a very large, shared table, because there's currently no cheap way to narrow it down to one customer first. That's not a caching problem. It's a schema gap, and the real fix is a proper one, adding that marker to the underlying table and indexing it, not a workaround bolted onto the read path.
That fix is scoped and understood. It is also not done yet, on purpose. A backfill across millions of rows in a table that gets written constantly deserves its own careful, deliberate migration, not a rushed afterthought tacked onto an analytics fix. Shipping the honest interim answer now was the right call over shipping the real fix badly under time pressure.
The interim answer is a short cache, applied only to those two specific views, that serves the last good answer instantly while quietly recomputing in the background and re-checking itself every 30 minutes. In practice that means almost every view of those two figures is either fresh or refreshing itself unseen, and the only time anyone genuinely waits for the real number is the very first time a brand-new customer ever opens that screen. After that, it's instant, for that customer, for good.
There's a real difference between "this is genuinely too expensive to compute on every click" and "we didn't want to think about it," and a snapshot-everything architecture tends to blur the two together. Once one part of a dashboard is precomputed, it's easy to precompute the rest by default, because it's simpler to build one pattern than to justify two. The discipline that's worth defending is the opposite one: default to live, measure honestly against real data before reaching for a cache, and use one only where the numbers actually force it, on the smallest possible surface.
Naming the part that's still slow, and saying plainly what would fix it and why it isn't fixed yet, isn't an admission of weakness. It's the alternative to the far more common move, which is to say nothing and let a customer discover the staleness themselves, usually at the worst possible moment, usually as a support ticket rather than a documented tradeoff.
If you're weighing a CRM or analytics platform for a guest database in the millions, ask the vendor a direct question: is this number live, or is it a snapshot, and if it's a snapshot, how stale can it get before anyone notices or says so. Most won't have a crisp answer, because most haven't had to build past the point where a live query gets genuinely expensive. The interesting engineering isn't avoiding that point. It's what a team does once it arrives there, and whether they tell you about it.
Contacts Analytics sits on the same guest record that powers GuestMaker's CDP, the identity layer that resolves one guest across every stay, every channel, and every property in a group before analytics ever gets to summarize anything. If you're curious how that record gets built in the first place, the guest record that never forgets covers the identity resolution underneath it.
Twenty minutes, your real properties, no generic demo environment.