Klantervaring
Trage widget
Klikken op datum, personen of tijdslot voelde traag aan — bij elke interactie werd alles opnieuw geladen.
Case study · reservatiesoftware · gebruikerservaring
Klantervaring
Trage widget
Klikken op datum, personen of tijdslot voelde traag aan — bij elke interactie werd alles opnieuw geladen.
Aanpak
Slimmer, niet meer
Dezelfde servers, minder werk per klik. Stabiele data hergebruiken, variabele data gericht en snel ophalen.
Resultaat
Directe respons
De widget reageert consistent bij herhaald klikken — ook bij drukke boekingsmomenten.
Probleem
In de widget zagen we dat dezelfde datum meerdere keren opnieuw gevalideerd werd. Wie een dag opende, één keer van aantal personen wisselde of naar een ander tijdslot klikte, forceerde telkens opnieuw dezelfde onderliggende controles.
Wat dat concreet betekende
Analyse
Stabiele data zoals shifts per restaurant en weekdag werd telkens opnieuw geladen.
Openingsstatus en sluitingsdagen werden herhaaldelijk gevalideerd in dezelfde gebruikersflow.
Capaciteit per tijdslot werd opgevraagd op een manier die lineair meer databasecalls veroorzaakte.
De widget werkte functioneel, maar betaalde die correctheid met onnodig veel werk per request.
De kern van de oplossing zat in een heldere opsplitsing: stabiele data naar cache, volatiele data kort cachen of direct ophalen. Daarmee werd het gedrag niet alleen sneller, maar ook beter voorspelbaar.
Oplossing
We hebben de widget op twee assen herwerkt: configuratiedata wordt hergebruikt vanuit cache, terwijl beschikbare tijdsloten gericht en kort worden opgehaald.
Stap 1 — hergebruik wat stabiel is
Shifts per restaurant en weekdag veranderen zelden. Die worden 10 minuten bewaard zodat ze niet bij elke klik opnieuw worden opgehaald.
Stap 2 — haal variabele data in één keer op
Capaciteit per tijdslot wordt niet meer één voor één opgevraagd, maar in één gerichte vraag aan de database.
Stap 3 — bewaar beschikbaarheid heel kort
Beschikbare tijdsloten worden 30 seconden bewaard: snel bij herhaald klikken, maar nooit te verouderd om betrouwbaar te zijn.
Queryflow vergelijking
Vóór: N+1 patroon (per tijdslot)
→ SELECT shifts WHERE date = ? (×1)
→ GET capacity tijdslot #1 (×1)
→ GET capacity tijdslot #2 (×1)
→ GET capacity tijdslot #3 (×1)
= N queries per interactie
Na: cache + batch (per request)
→ GET shifts (cache hit ✓) 0 queries
→ SELECT capacity WHERE id IN (1,2,3)
= 1 query per interactie
Cache TTL-strategie
Shifts (stabiel) → TTL 10 min
Openingsstatus → TTL 5 min
Beschikbare sloten → TTL 1 min
Trade-off
Pro
Minder databasehits, sneller antwoord voor herhaalde interacties en een eenvoudige implementatie zonder extra infrastructuur.
Con
Je moet bewust omgaan met verouderde data en expliciet nadenken over invalidatie zodra reservaties worden aangemaakt of gewijzigd.
Architectuurkeuze
Mijn mening is duidelijk: voor publieke widgets wil je eerst het aantal queries reduceren en slim cachen, voordat je naar complexere infrastructuur grijpt. Dat levert meestal sneller resultaat op en houdt de oplossing eenvoudiger te debuggen.
Waarom dit werkt
Eén batchquery is voorspelbaarder dan meerdere kleine queries. Een korte cache op stabiele of semi-stabiele data levert meteen winst zonder dat je architectuur onnodig zwaar wordt.
Resultaat
Shifts worden niet meer bij elke interactie opnieuw uit de database gehaald.
Capaciteit wordt niet langer per tijdslot apart opgehaald, maar in één batch geladen.
De widget reageert consistenter bij herhaald klikken op datum, aantal personen en tijdslot.
De winst kwam uit query-reductie en gerichte caching, niet uit zwaardere infrastructuur.
Lessons learned
Cache alleen data die stabiel genoeg is om kort verouderd te mogen zijn.
Gebruik batch loading voor lijsten die anders per item een query triggeren.
Kies een korte TTL voor data die snel kan wijzigen, zoals beschikbare tijdsloten.
Denk vooraf na over expliciete invalidatie na reserveren of wijzigen.
Test widgets met meerdere opeenvolgende requests, niet alleen met één losse call.
Takeaway
Cache stabiele data, batch-load variabele data en hou invalidatie bewust en klein. Dat geeft meestal sneller resultaat dan meteen infrastructuur verzwaren, en het houdt je widget veel beter beheersbaar.
Voor de technisch geïnteresseerde
De onderstaande codevoorbeelden tonen hoe caching en batch loading concreet zijn uitgewerkt in C# met IMemoryCache.
1. Shifts cachen per restaurant en weekdag
string shiftsCacheKey = $"shifts:{restaurantId}:{weekdag}";
if (!_memoryCache.TryGetValue(shiftsCacheKey, out List<Shift> shiftsForDate))
{
shiftsForDate = await _context.Shifts
.Where(s => s.RestaurantId == restaurantId
&& s.Weekdag == weekdag
&& s.IsActief)
.Include(s => s.ShiftTijdsloten)
.ToListAsync();
_memoryCache.Set(shiftsCacheKey, shiftsForDate, TimeSpan.FromMinutes(10));
}2. Capaciteit batch-loaden in één query
var tijdslotIds = tijdsloten.Select(ts => ts.Id).ToList();
var capaciteiten = await _context.ReservatieCapaciteit
.Where(rc => tijdslotIds.Contains(rc.ShiftTijdslotId) && rc.Datum.Date == datum.Date)
.ToDictionaryAsync(rc => rc.ShiftTijdslotId);3. Korte cache voor beschikbare tijdsloten
string cacheKey = $"tijdsloten:{restaurantId}:{datum:yyyy-MM-dd}:{aantalPersonen}";
if (_memoryCache.TryGetValue(cacheKey, out List<object> cachedResults))
{
return Ok(cachedResults);
}
Plan een vrijblijvende digitale kennismaking met Mitch en ontdek wat wij voor jouw organisatie kunnen betekenen.