[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-nl-\u002Fblog\u002Frijmwoordenboek-caching-optimization-\u002Fblog\u002Frijmwoordenboek-caching-optimization":3,"blog-post-surround-nl-\u002Fblog\u002Frijmwoordenboek-caching-optimization-\u002Fblog\u002Frijmwoordenboek-caching-optimization":1467,"related-posts-nl-\u002Fblog\u002Frijmwoordenboek-caching-optimization-\u002Fblog\u002Frijmwoordenboek-caching-optimization":1474},{"id":4,"title":5,"authors":6,"badge":13,"body":15,"categories":1448,"date":1450,"description":1451,"extension":1452,"image":1453,"meta":1455,"navigation":403,"path":1456,"readingTime":207,"seo":1457,"stem":1458,"tags":1459,"__hash__":1466},"posts_nl\u002Fblog\u002F7.rijmwoordenboek-caching-optimization.md","Rijmwoordenboek: Pagina's Onder 15ms Met Betere Caching",[7],{"name":8,"to":9,"avatar":10,"bio":12},"Rob Schoenaker","https:\u002F\u002Flinkedin.com\u002Fin\u002Frobschoenaker",{"src":11},"\u002Fimages\u002Fteam\u002Frob.jpg","Managing Partner bij UpstreamAds en Partner bij Ludulicious B.V. met meer dan 20 jaar ervaring in softwareontwikkeling, gespecialiseerd in .NET Core, ServiceStack, C# en database design.",{"label":14},"Caching",{"type":16,"value":17,"toc":1421},"minimark",[18,23,27,33,49,54,108,119,123,126,131,145,149,154,157,292,297,317,323,327,330,455,459,482,488,492,495,538,542,562,567,571,575,578,625,629,632,750,754,768,773,777,781,784,860,864,867,1046,1050,1064,1069,1073,1173,1177,1181,1192,1196,1207,1211,1222,1226,1237,1241,1252,1256,1259,1330,1334,1337,1340,1343,1363,1368,1376,1408,1411,1417],[19,20,22],"h2",{"id":21},"het-probleem-paginalaadtijden-doden-gebruikerservaring","Het Probleem: Paginalaadtijden Doden Gebruikerservaring",[24,25,26],"p",{},"In 2021 stond Van Dale Rijmwoordenboek voor een kritieke gebruikerservaring uitdaging. Zelfs na het optimaliseren van onze fonetische zoekqueries waren paginalaadtijden nog steeds meer dan 100ms. Voor een webapplicatie was dit onacceptabel.",[24,28,29],{},[30,31,32],"strong",{},"De Uitdaging:",[34,35,36,40,43,46],"ul",{},[37,38,39],"li",{},"Paginalaadtijden gemiddeld 100-150ms",[37,41,42],{},"Gebruikers verwachten directe paginaresponsen",[37,44,45],{},"Database queries draaien bij elke paginaload",[37,47,48],{},"Geen caching strategie op zijn plaats",[24,50,51],{},[30,52,53],{},"De Cijfers:",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- Elke paginaload raakte de database\nSELECT word, phonetic_code, frequency, definition\nFROM words \nWHERE phonetic_code LIKE 'KAT%'\nORDER BY frequency DESC\nLIMIT 20;\n-- Uitvoeringstijd: 85ms per paginaload\n","sql","",[62,63,64,72,78,84,90,96,102],"code",{"__ignoreMap":60},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,71],{},"-- Elke paginaload raakte de database\n",[65,73,75],{"class":67,"line":74},2,[65,76,77],{},"SELECT word, phonetic_code, frequency, definition\n",[65,79,81],{"class":67,"line":80},3,[65,82,83],{},"FROM words \n",[65,85,87],{"class":67,"line":86},4,[65,88,89],{},"WHERE phonetic_code LIKE 'KAT%'\n",[65,91,93],{"class":67,"line":92},5,[65,94,95],{},"ORDER BY frequency DESC\n",[65,97,99],{"class":67,"line":98},6,[65,100,101],{},"LIMIT 20;\n",[65,103,105],{"class":67,"line":104},7,[65,106,107],{},"-- Uitvoeringstijd: 85ms per paginaload\n",[24,109,110],{},[111,112],"img",{"alt":113,"className":114,"height":116,"src":117,"width":118},"Rijmwoordenboek caching performance",[115],"rounded-lg",600,"https:\u002F\u002Fpicsum.photos\u002Fid\u002F9\u002F1000\u002F600",1000,[19,120,122],{"id":121},"de-oorzaak-geen-caching-strategie","De Oorzaak: Geen Caching Strategie",[24,124,125],{},"Het probleem was duidelijk uit onze monitoring:",[24,127,128],{},[30,129,130],{},"Wat er gebeurde:",[34,132,133,136,139,142],{},[37,134,135],{},"Elke paginaload voerde verse database queries uit",[37,137,138],{},"Geen applicatie-level caching geïmplementeerd",[37,140,141],{},"Database werd geraakt voor identieke queries herhaaldelijk",[37,143,144],{},"Responsetijden varieerden op basis van databasebelasting",[19,146,148],{"id":147},"de-oplossing-multi-layer-caching-strategie","De Oplossing: Multi-Layer Caching Strategie",[150,151,153],"h3",{"id":152},"stap-1-applicatie-level-caching-met-redis","Stap 1: Applicatie-Level Caching Met Redis",[24,155,156],{},"De eerste doorbraak kwam met Redis caching:",[55,158,162],{"className":159,"code":160,"language":161,"meta":60,"style":60},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Applicatie-level caching implementatie\npublic async Task\u003CList\u003CWord>> GetWordsByPhoneticCode(string phoneticCode)\n{\n    var cacheKey = $\"words:phonetic:{phoneticCode}\";\n    \n    \u002F\u002F Probeer eerst uit cache te halen\n    var cachedWords = await _redis.GetAsync\u003CList\u003CWord>>(cacheKey);\n    if (cachedWords != null)\n    {\n        return cachedWords; \u002F\u002F Retourneer gecachte resultaten direct\n    }\n    \n    \u002F\u002F Als niet in cache, query database\n    var words = await _database.QueryAsync\u003CWord>(\n        \"SELECT word, phonetic_code, frequency FROM words WHERE phonetic_code LIKE @code ORDER BY frequency DESC LIMIT 20\",\n        new { code = phoneticCode + \"%\" }\n    );\n    \n    \u002F\u002F Cache het resultaat voor 5 minuten\n    await _redis.SetAsync(cacheKey, words, TimeSpan.FromMinutes(5));\n    \n    return words;\n}\n","csharp",[62,163,164,169,174,179,184,189,194,199,205,211,217,223,228,234,240,246,252,258,263,269,275,280,286],{"__ignoreMap":60},[65,165,166],{"class":67,"line":68},[65,167,168],{},"\u002F\u002F Applicatie-level caching implementatie\n",[65,170,171],{"class":67,"line":74},[65,172,173],{},"public async Task\u003CList\u003CWord>> GetWordsByPhoneticCode(string phoneticCode)\n",[65,175,176],{"class":67,"line":80},[65,177,178],{},"{\n",[65,180,181],{"class":67,"line":86},[65,182,183],{},"    var cacheKey = $\"words:phonetic:{phoneticCode}\";\n",[65,185,186],{"class":67,"line":92},[65,187,188],{},"    \n",[65,190,191],{"class":67,"line":98},[65,192,193],{},"    \u002F\u002F Probeer eerst uit cache te halen\n",[65,195,196],{"class":67,"line":104},[65,197,198],{},"    var cachedWords = await _redis.GetAsync\u003CList\u003CWord>>(cacheKey);\n",[65,200,202],{"class":67,"line":201},8,[65,203,204],{},"    if (cachedWords != null)\n",[65,206,208],{"class":67,"line":207},9,[65,209,210],{},"    {\n",[65,212,214],{"class":67,"line":213},10,[65,215,216],{},"        return cachedWords; \u002F\u002F Retourneer gecachte resultaten direct\n",[65,218,220],{"class":67,"line":219},11,[65,221,222],{},"    }\n",[65,224,226],{"class":67,"line":225},12,[65,227,188],{},[65,229,231],{"class":67,"line":230},13,[65,232,233],{},"    \u002F\u002F Als niet in cache, query database\n",[65,235,237],{"class":67,"line":236},14,[65,238,239],{},"    var words = await _database.QueryAsync\u003CWord>(\n",[65,241,243],{"class":67,"line":242},15,[65,244,245],{},"        \"SELECT word, phonetic_code, frequency FROM words WHERE phonetic_code LIKE @code ORDER BY frequency DESC LIMIT 20\",\n",[65,247,249],{"class":67,"line":248},16,[65,250,251],{},"        new { code = phoneticCode + \"%\" }\n",[65,253,255],{"class":67,"line":254},17,[65,256,257],{},"    );\n",[65,259,261],{"class":67,"line":260},18,[65,262,188],{},[65,264,266],{"class":67,"line":265},19,[65,267,268],{},"    \u002F\u002F Cache het resultaat voor 5 minuten\n",[65,270,272],{"class":67,"line":271},20,[65,273,274],{},"    await _redis.SetAsync(cacheKey, words, TimeSpan.FromMinutes(5));\n",[65,276,278],{"class":67,"line":277},21,[65,279,188],{},[65,281,283],{"class":67,"line":282},22,[65,284,285],{},"    return words;\n",[65,287,289],{"class":67,"line":288},23,[65,290,291],{},"}\n",[24,293,294],{},[30,295,296],{},"Waarom Dit Werkt:",[34,298,299,305,311,314],{},[37,300,301,304],{},[62,302,303],{},"Redis.GetAsync()",": Controleert cache eerst, retourneert direct als gevonden",[37,306,307,310],{},[62,308,309],{},"TimeSpan.FromMinutes(5)",": Cachet resultaten voor 5 minuten, balancerend tussen versheid en performance",[37,312,313],{},"Elimineert database hits voor herhaalde queries",[37,315,316],{},"Vermindert responsetijden dramatisch voor gecachte data",[24,318,319,322],{},[30,320,321],{},"Direct Resultaat:"," Gecachte queries daalden van 85ms naar 2ms (42x verbetering)",[150,324,326],{"id":325},"stap-2-database-query-result-caching","Stap 2: Database Query Result Caching",[24,328,329],{},"Voor veelgebruikte fonetische patronen implementeerden we database-level caching:",[55,331,333],{"className":57,"code":332,"language":59,"meta":60,"style":60},"-- Maak materialized view voor veelvoorkomende fonetische patronen\nCREATE MATERIALIZED VIEW words_common_patterns AS\nSELECT phonetic_code, \n       array_agg(word ORDER BY frequency DESC) as words,\n       array_agg(frequency ORDER BY frequency DESC) as frequencies\nFROM words \nWHERE phonetic_code IN (\n    SELECT phonetic_code \n    FROM words \n    GROUP BY phonetic_code \n    HAVING count(*) > 10\n)\nGROUP BY phonetic_code;\n\n-- Ververs materialized view elk uur\nCREATE OR REPLACE FUNCTION refresh_words_patterns()\nRETURNS void AS $$\nBEGIN\n    REFRESH MATERIALIZED VIEW CONCURRENTLY words_common_patterns;\nEND;\n$$ LANGUAGE plpgsql;\n\n-- Plan verversing elk uur\nSELECT cron.schedule('refresh-words-patterns', '0 * * * *', 'SELECT refresh_words_patterns();');\n",[62,334,335,340,345,350,355,360,364,369,374,379,384,389,394,399,405,410,415,420,425,430,435,440,444,449],{"__ignoreMap":60},[65,336,337],{"class":67,"line":68},[65,338,339],{},"-- Maak materialized view voor veelvoorkomende fonetische patronen\n",[65,341,342],{"class":67,"line":74},[65,343,344],{},"CREATE MATERIALIZED VIEW words_common_patterns AS\n",[65,346,347],{"class":67,"line":80},[65,348,349],{},"SELECT phonetic_code, \n",[65,351,352],{"class":67,"line":86},[65,353,354],{},"       array_agg(word ORDER BY frequency DESC) as words,\n",[65,356,357],{"class":67,"line":92},[65,358,359],{},"       array_agg(frequency ORDER BY frequency DESC) as frequencies\n",[65,361,362],{"class":67,"line":98},[65,363,83],{},[65,365,366],{"class":67,"line":104},[65,367,368],{},"WHERE phonetic_code IN (\n",[65,370,371],{"class":67,"line":201},[65,372,373],{},"    SELECT phonetic_code \n",[65,375,376],{"class":67,"line":207},[65,377,378],{},"    FROM words \n",[65,380,381],{"class":67,"line":213},[65,382,383],{},"    GROUP BY phonetic_code \n",[65,385,386],{"class":67,"line":219},[65,387,388],{},"    HAVING count(*) > 10\n",[65,390,391],{"class":67,"line":225},[65,392,393],{},")\n",[65,395,396],{"class":67,"line":230},[65,397,398],{},"GROUP BY phonetic_code;\n",[65,400,401],{"class":67,"line":236},[65,402,404],{"emptyLinePlaceholder":403},true,"\n",[65,406,407],{"class":67,"line":242},[65,408,409],{},"-- Ververs materialized view elk uur\n",[65,411,412],{"class":67,"line":248},[65,413,414],{},"CREATE OR REPLACE FUNCTION refresh_words_patterns()\n",[65,416,417],{"class":67,"line":254},[65,418,419],{},"RETURNS void AS $$\n",[65,421,422],{"class":67,"line":260},[65,423,424],{},"BEGIN\n",[65,426,427],{"class":67,"line":265},[65,428,429],{},"    REFRESH MATERIALIZED VIEW CONCURRENTLY words_common_patterns;\n",[65,431,432],{"class":67,"line":271},[65,433,434],{},"END;\n",[65,436,437],{"class":67,"line":277},[65,438,439],{},"$$ LANGUAGE plpgsql;\n",[65,441,442],{"class":67,"line":282},[65,443,404],{"emptyLinePlaceholder":403},[65,445,446],{"class":67,"line":288},[65,447,448],{},"-- Plan verversing elk uur\n",[65,450,452],{"class":67,"line":451},24,[65,453,454],{},"SELECT cron.schedule('refresh-words-patterns', '0 * * * *', 'SELECT refresh_words_patterns();');\n",[24,456,457],{},[30,458,296],{},[34,460,461,467,473,479],{},[37,462,463,466],{},[62,464,465],{},"MATERIALIZED VIEW",": Pre-computeert veelvoorkomende fonetische patronen",[37,468,469,472],{},[62,470,471],{},"REFRESH MATERIALIZED VIEW CONCURRENTLY",": Updateert zonder reads te blokkeren",[37,474,475,478],{},[62,476,477],{},"cron.schedule()",": Ververs data automatisch elk uur",[37,480,481],{},"Elimineert dure GROUP BY operaties voor veelvoorkomende queries",[24,483,484,487],{},[30,485,486],{},"Resultaat:"," Veelvoorkomende patroon queries verbeterden naar 15ms (5.7x verbetering)",[150,489,491],{"id":490},"stap-3-http-response-caching","Stap 3: HTTP Response Caching",[24,493,494],{},"Voor statische fonetische data implementeerden we HTTP caching:",[55,496,498],{"className":159,"code":497,"language":161,"meta":60,"style":60},"\u002F\u002F HTTP response caching implementatie\n[HttpGet(\"phonetic\u002F{phoneticCode}\")]\n[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]\npublic async Task\u003CIActionResult> GetWordsByPhonetic(string phoneticCode)\n{\n    var words = await GetWordsByPhoneticCode(phoneticCode);\n    return Ok(words);\n}\n",[62,499,500,505,510,515,520,524,529,534],{"__ignoreMap":60},[65,501,502],{"class":67,"line":68},[65,503,504],{},"\u002F\u002F HTTP response caching implementatie\n",[65,506,507],{"class":67,"line":74},[65,508,509],{},"[HttpGet(\"phonetic\u002F{phoneticCode}\")]\n",[65,511,512],{"class":67,"line":80},[65,513,514],{},"[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]\n",[65,516,517],{"class":67,"line":86},[65,518,519],{},"public async Task\u003CIActionResult> GetWordsByPhonetic(string phoneticCode)\n",[65,521,522],{"class":67,"line":92},[65,523,178],{},[65,525,526],{"class":67,"line":98},[65,527,528],{},"    var words = await GetWordsByPhoneticCode(phoneticCode);\n",[65,530,531],{"class":67,"line":104},[65,532,533],{},"    return Ok(words);\n",[65,535,536],{"class":67,"line":201},[65,537,291],{},[24,539,540],{},[30,541,296],{},[34,543,544,550,556,559],{},[37,545,546,549],{},[62,547,548],{},"ResponseCache(Duration = 300)",": Cachet HTTP responses voor 5 minuten",[37,551,552,555],{},[62,553,554],{},"ResponseCacheLocation.Any",": Staat caching toe op elk niveau (browser, CDN, proxy)",[37,557,558],{},"Vermindert serverbelasting voor herhaalde requests",[37,560,561],{},"Verbeterd responsetijden voor gebruikers met gecachte responses",[24,563,564,566],{},[30,565,486],{}," HTTP responsetijden verbeterden naar 8ms (12.5x verbetering)",[19,568,570],{"id":569},"de-game-changer-slimme-cache-invalidatie","De Game Changer: Slimme Cache Invalidatie",[150,572,574],{"id":573},"het-probleem-verouderde-data-issues","Het Probleem: Verouderde Data Issues",[24,576,577],{},"Met caching op zijn plaats stonden we voor verouderde data problemen:",[55,579,581],{"className":159,"code":580,"language":161,"meta":60,"style":60},"\u002F\u002F Probleem: Cache niet geïnvalideerd wanneer data verandert\npublic async Task UpdateWordFrequency(int wordId, int newFrequency)\n{\n    await _database.ExecuteAsync(\n        \"UPDATE words SET frequency = @frequency WHERE id = @id\",\n        new { frequency = newFrequency, id = wordId }\n    );\n    \u002F\u002F Cache niet geïnvalideerd - gebruikers zien verouderde data!\n}\n",[62,582,583,588,593,597,602,607,612,616,621],{"__ignoreMap":60},[65,584,585],{"class":67,"line":68},[65,586,587],{},"\u002F\u002F Probleem: Cache niet geïnvalideerd wanneer data verandert\n",[65,589,590],{"class":67,"line":74},[65,591,592],{},"public async Task UpdateWordFrequency(int wordId, int newFrequency)\n",[65,594,595],{"class":67,"line":80},[65,596,178],{},[65,598,599],{"class":67,"line":86},[65,600,601],{},"    await _database.ExecuteAsync(\n",[65,603,604],{"class":67,"line":92},[65,605,606],{},"        \"UPDATE words SET frequency = @frequency WHERE id = @id\",\n",[65,608,609],{"class":67,"line":98},[65,610,611],{},"        new { frequency = newFrequency, id = wordId }\n",[65,613,614],{"class":67,"line":104},[65,615,257],{},[65,617,618],{"class":67,"line":201},[65,619,620],{},"    \u002F\u002F Cache niet geïnvalideerd - gebruikers zien verouderde data!\n",[65,622,623],{"class":67,"line":207},[65,624,291],{},[150,626,628],{"id":627},"de-oplossing-intelligente-cache-invalidatie","De Oplossing: Intelligente Cache Invalidatie",[24,630,631],{},"We implementeerden slimme cache invalidatie:",[55,633,635],{"className":159,"code":634,"language":161,"meta":60,"style":60},"\u002F\u002F Slimme cache invalidatie implementatie\npublic async Task UpdateWordFrequency(int wordId, int newFrequency)\n{\n    \u002F\u002F Haal het woord op om zijn fonetische code te vinden\n    var word = await _database.QueryFirstAsync\u003CWord>(\n        \"SELECT phonetic_code FROM words WHERE id = @id\", \n        new { id = wordId }\n    );\n    \n    \u002F\u002F Update de database\n    await _database.ExecuteAsync(\n        \"UPDATE words SET frequency = @frequency WHERE id = @id\",\n        new { frequency = newFrequency, id = wordId }\n    );\n    \n    \u002F\u002F Invalideer gerelateerde caches\n    var cacheKey = $\"words:phonetic:{word.PhoneticCode}\";\n    await _redis.RemoveAsync(cacheKey);\n    \n    \u002F\u002F Invalideer ook patroon cache als dit veelvoorkomende patronen beïnvloedt\n    if (newFrequency > 1000)\n    {\n        await _redis.RemoveAsync(\"words:common_patterns\");\n    }\n}\n",[62,636,637,642,646,650,655,660,665,670,674,678,683,687,691,695,699,703,708,713,718,722,727,732,736,741,745],{"__ignoreMap":60},[65,638,639],{"class":67,"line":68},[65,640,641],{},"\u002F\u002F Slimme cache invalidatie implementatie\n",[65,643,644],{"class":67,"line":74},[65,645,592],{},[65,647,648],{"class":67,"line":80},[65,649,178],{},[65,651,652],{"class":67,"line":86},[65,653,654],{},"    \u002F\u002F Haal het woord op om zijn fonetische code te vinden\n",[65,656,657],{"class":67,"line":92},[65,658,659],{},"    var word = await _database.QueryFirstAsync\u003CWord>(\n",[65,661,662],{"class":67,"line":98},[65,663,664],{},"        \"SELECT phonetic_code FROM words WHERE id = @id\", \n",[65,666,667],{"class":67,"line":104},[65,668,669],{},"        new { id = wordId }\n",[65,671,672],{"class":67,"line":201},[65,673,257],{},[65,675,676],{"class":67,"line":207},[65,677,188],{},[65,679,680],{"class":67,"line":213},[65,681,682],{},"    \u002F\u002F Update de database\n",[65,684,685],{"class":67,"line":219},[65,686,601],{},[65,688,689],{"class":67,"line":225},[65,690,606],{},[65,692,693],{"class":67,"line":230},[65,694,611],{},[65,696,697],{"class":67,"line":236},[65,698,257],{},[65,700,701],{"class":67,"line":242},[65,702,188],{},[65,704,705],{"class":67,"line":248},[65,706,707],{},"    \u002F\u002F Invalideer gerelateerde caches\n",[65,709,710],{"class":67,"line":254},[65,711,712],{},"    var cacheKey = $\"words:phonetic:{word.PhoneticCode}\";\n",[65,714,715],{"class":67,"line":260},[65,716,717],{},"    await _redis.RemoveAsync(cacheKey);\n",[65,719,720],{"class":67,"line":265},[65,721,188],{},[65,723,724],{"class":67,"line":271},[65,725,726],{},"    \u002F\u002F Invalideer ook patroon cache als dit veelvoorkomende patronen beïnvloedt\n",[65,728,729],{"class":67,"line":277},[65,730,731],{},"    if (newFrequency > 1000)\n",[65,733,734],{"class":67,"line":282},[65,735,210],{},[65,737,738],{"class":67,"line":288},[65,739,740],{},"        await _redis.RemoveAsync(\"words:common_patterns\");\n",[65,742,743],{"class":67,"line":451},[65,744,222],{},[65,746,748],{"class":67,"line":747},25,[65,749,291],{},[24,751,752],{},[30,753,296],{},[34,755,756,759,762,765],{},[37,757,758],{},"Invalideert specifieke fonetische code caches wanneer data verandert",[37,760,761],{},"Invalideert patroon caches wanneer frequentie thresholds veranderen",[37,763,764],{},"Zorgt ervoor dat gebruikers altijd verse data zien",[37,766,767],{},"Behoudt cache performance voordelen",[24,769,770,772],{},[30,771,486],{}," Cache hit rate verbeterde naar 95% terwijl data versheid behouden bleef",[19,774,776],{"id":775},"de-finale-optimalisatie-preventieve-caching","De Finale Optimalisatie: Preventieve Caching",[150,778,780],{"id":779},"het-probleem-cache-misses-tijdens-piekgebruik","Het Probleem: Cache Misses Tijdens Piekgebruik",[24,782,783],{},"Tijdens piekgebruik veroorzaakten cache misses nog steeds langzame responses:",[55,785,787],{"className":159,"code":786,"language":161,"meta":60,"style":60},"\u002F\u002F Probleem: Cache misses tijdens piekgebruik\npublic async Task\u003CList\u003CWord>> GetWordsByPhoneticCode(string phoneticCode)\n{\n    var cacheKey = $\"words:phonetic:{phoneticCode}\";\n    var cachedWords = await _redis.GetAsync\u003CList\u003CWord>>(cacheKey);\n    \n    if (cachedWords == null)\n    {\n        \u002F\u002F Cache miss - langzame database query tijdens piekgebruik\n        var words = await _database.QueryAsync\u003CWord>(...);\n        await _redis.SetAsync(cacheKey, words, TimeSpan.FromMinutes(5));\n        return words;\n    }\n    \n    return cachedWords;\n}\n",[62,788,789,794,798,802,806,810,814,819,823,828,833,838,843,847,851,856],{"__ignoreMap":60},[65,790,791],{"class":67,"line":68},[65,792,793],{},"\u002F\u002F Probleem: Cache misses tijdens piekgebruik\n",[65,795,796],{"class":67,"line":74},[65,797,173],{},[65,799,800],{"class":67,"line":80},[65,801,178],{},[65,803,804],{"class":67,"line":86},[65,805,183],{},[65,807,808],{"class":67,"line":92},[65,809,198],{},[65,811,812],{"class":67,"line":98},[65,813,188],{},[65,815,816],{"class":67,"line":104},[65,817,818],{},"    if (cachedWords == null)\n",[65,820,821],{"class":67,"line":201},[65,822,210],{},[65,824,825],{"class":67,"line":207},[65,826,827],{},"        \u002F\u002F Cache miss - langzame database query tijdens piekgebruik\n",[65,829,830],{"class":67,"line":213},[65,831,832],{},"        var words = await _database.QueryAsync\u003CWord>(...);\n",[65,834,835],{"class":67,"line":219},[65,836,837],{},"        await _redis.SetAsync(cacheKey, words, TimeSpan.FromMinutes(5));\n",[65,839,840],{"class":67,"line":225},[65,841,842],{},"        return words;\n",[65,844,845],{"class":67,"line":230},[65,846,222],{},[65,848,849],{"class":67,"line":236},[65,850,188],{},[65,852,853],{"class":67,"line":242},[65,854,855],{},"    return cachedWords;\n",[65,857,858],{"class":67,"line":248},[65,859,291],{},[150,861,863],{"id":862},"de-oplossing-background-cache-warming","De Oplossing: Background Cache Warming",[24,865,866],{},"We implementeerden background cache warming:",[55,868,870],{"className":159,"code":869,"language":161,"meta":60,"style":60},"\u002F\u002F Background cache warming implementatie\npublic class CacheWarmingService : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            \u002F\u002F Haal meest populaire fonetische codes op\n            var popularCodes = await _database.QueryAsync\u003Cstring>(\n                \"SELECT phonetic_code FROM words GROUP BY phonetic_code ORDER BY count(*) DESC LIMIT 100\"\n            );\n            \n            \u002F\u002F Pre-warm cache voor populaire codes\n            foreach (var code in popularCodes)\n            {\n                var cacheKey = $\"words:phonetic:{code}\";\n                var exists = await _redis.ExistsAsync(cacheKey);\n                \n                if (!exists)\n                {\n                    var words = await _database.QueryAsync\u003CWord>(\n                        \"SELECT word, phonetic_code, frequency FROM words WHERE phonetic_code LIKE @code ORDER BY frequency DESC LIMIT 20\",\n                        new { code = code + \"%\" }\n                    );\n                    \n                    await _redis.SetAsync(cacheKey, words, TimeSpan.FromMinutes(5));\n                }\n            }\n            \n            \u002F\u002F Wacht 10 minuten voor volgende warming cyclus\n            await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken);\n        }\n    }\n}\n",[62,871,872,877,882,886,891,895,900,905,910,915,920,925,930,935,940,945,950,955,960,965,970,975,980,985,990,995,1001,1007,1013,1018,1024,1030,1036,1041],{"__ignoreMap":60},[65,873,874],{"class":67,"line":68},[65,875,876],{},"\u002F\u002F Background cache warming implementatie\n",[65,878,879],{"class":67,"line":74},[65,880,881],{},"public class CacheWarmingService : BackgroundService\n",[65,883,884],{"class":67,"line":80},[65,885,178],{},[65,887,888],{"class":67,"line":86},[65,889,890],{},"    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n",[65,892,893],{"class":67,"line":92},[65,894,210],{},[65,896,897],{"class":67,"line":98},[65,898,899],{},"        while (!stoppingToken.IsCancellationRequested)\n",[65,901,902],{"class":67,"line":104},[65,903,904],{},"        {\n",[65,906,907],{"class":67,"line":201},[65,908,909],{},"            \u002F\u002F Haal meest populaire fonetische codes op\n",[65,911,912],{"class":67,"line":207},[65,913,914],{},"            var popularCodes = await _database.QueryAsync\u003Cstring>(\n",[65,916,917],{"class":67,"line":213},[65,918,919],{},"                \"SELECT phonetic_code FROM words GROUP BY phonetic_code ORDER BY count(*) DESC LIMIT 100\"\n",[65,921,922],{"class":67,"line":219},[65,923,924],{},"            );\n",[65,926,927],{"class":67,"line":225},[65,928,929],{},"            \n",[65,931,932],{"class":67,"line":230},[65,933,934],{},"            \u002F\u002F Pre-warm cache voor populaire codes\n",[65,936,937],{"class":67,"line":236},[65,938,939],{},"            foreach (var code in popularCodes)\n",[65,941,942],{"class":67,"line":242},[65,943,944],{},"            {\n",[65,946,947],{"class":67,"line":248},[65,948,949],{},"                var cacheKey = $\"words:phonetic:{code}\";\n",[65,951,952],{"class":67,"line":254},[65,953,954],{},"                var exists = await _redis.ExistsAsync(cacheKey);\n",[65,956,957],{"class":67,"line":260},[65,958,959],{},"                \n",[65,961,962],{"class":67,"line":265},[65,963,964],{},"                if (!exists)\n",[65,966,967],{"class":67,"line":271},[65,968,969],{},"                {\n",[65,971,972],{"class":67,"line":277},[65,973,974],{},"                    var words = await _database.QueryAsync\u003CWord>(\n",[65,976,977],{"class":67,"line":282},[65,978,979],{},"                        \"SELECT word, phonetic_code, frequency FROM words WHERE phonetic_code LIKE @code ORDER BY frequency DESC LIMIT 20\",\n",[65,981,982],{"class":67,"line":288},[65,983,984],{},"                        new { code = code + \"%\" }\n",[65,986,987],{"class":67,"line":451},[65,988,989],{},"                    );\n",[65,991,992],{"class":67,"line":747},[65,993,994],{},"                    \n",[65,996,998],{"class":67,"line":997},26,[65,999,1000],{},"                    await _redis.SetAsync(cacheKey, words, TimeSpan.FromMinutes(5));\n",[65,1002,1004],{"class":67,"line":1003},27,[65,1005,1006],{},"                }\n",[65,1008,1010],{"class":67,"line":1009},28,[65,1011,1012],{},"            }\n",[65,1014,1016],{"class":67,"line":1015},29,[65,1017,929],{},[65,1019,1021],{"class":67,"line":1020},30,[65,1022,1023],{},"            \u002F\u002F Wacht 10 minuten voor volgende warming cyclus\n",[65,1025,1027],{"class":67,"line":1026},31,[65,1028,1029],{},"            await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken);\n",[65,1031,1033],{"class":67,"line":1032},32,[65,1034,1035],{},"        }\n",[65,1037,1039],{"class":67,"line":1038},33,[65,1040,222],{},[65,1042,1044],{"class":67,"line":1043},34,[65,1045,291],{},[24,1047,1048],{},[30,1049,296],{},[34,1051,1052,1055,1058,1061],{},[37,1053,1054],{},"Pre-warmt cache voor meest populaire fonetische codes",[37,1056,1057],{},"Draait op de achtergrond zonder gebruikersrequests te beïnvloeden",[37,1059,1060],{},"Vermindert cache misses tijdens piekgebruik",[37,1062,1063],{},"Zorgt ervoor dat populaire queries altijd gecached zijn",[24,1065,1066,1068],{},[30,1067,486],{}," Cache hit rate verbeterde naar 98%, responsetijden consistent onder 15ms",[19,1070,1072],{"id":1071},"performance-resultaten-samenvatting","Performance Resultaten Samenvatting",[1074,1075,1076,1092],"table",{},[1077,1078,1079],"thead",{},[1080,1081,1082,1086,1089],"tr",{},[1083,1084,1085],"th",{},"Optimalisatie Stap",[1083,1087,1088],{},"Responsetijd",[1083,1090,1091],{},"Verbetering",[1093,1094,1095,1109,1122,1135,1148,1159],"tbody",{},[1080,1096,1097,1103,1106],{},[1098,1099,1100],"td",{},[30,1101,1102],{},"Origineel (Geen Caching)",[1098,1104,1105],{},"100-150ms",[1098,1107,1108],{},"Baseline",[1080,1110,1111,1116,1119],{},[1098,1112,1113],{},[30,1114,1115],{},"Redis Applicatie Caching",[1098,1117,1118],{},"2ms",[1098,1120,1121],{},"50-75x sneller",[1080,1123,1124,1129,1132],{},[1098,1125,1126],{},[30,1127,1128],{},"Database Materialized Views",[1098,1130,1131],{},"15ms",[1098,1133,1134],{},"6.7-10x sneller",[1080,1136,1137,1142,1145],{},[1098,1138,1139],{},[30,1140,1141],{},"HTTP Response Caching",[1098,1143,1144],{},"8ms",[1098,1146,1147],{},"12.5-18.8x sneller",[1080,1149,1150,1155,1157],{},[1098,1151,1152],{},[30,1153,1154],{},"Slimme Cache Invalidatie",[1098,1156,1118],{},[1098,1158,1121],{},[1080,1160,1161,1166,1169],{},[1098,1162,1163],{},[30,1164,1165],{},"Background Cache Warming",[1098,1167,1168],{},"\u003C15ms",[1098,1170,1171],{},[30,1172,1134],{},[19,1174,1176],{"id":1175},"belangrijkste-lessen-geleerd","Belangrijkste Lessen Geleerd",[150,1178,1180],{"id":1179},"_1-multi-layer-caching-is-essentieel","1. Multi-Layer Caching Is Essentieel",[34,1182,1183,1186,1189],{},[37,1184,1185],{},"Applicatie-level caching elimineert database hits",[37,1187,1188],{},"Database-level caching optimaliseert dure queries",[37,1190,1191],{},"HTTP-level caching vermindert serverbelasting",[150,1193,1195],{"id":1194},"_2-cache-invalidatie-strategie-maakt-uit","2. Cache Invalidatie Strategie Maakt Uit",[34,1197,1198,1201,1204],{},[37,1199,1200],{},"Slimme invalidatie zorgt voor data versheid",[37,1202,1203],{},"Invalideer gerelateerde caches wanneer data verandert",[37,1205,1206],{},"Balanceer cache performance met data nauwkeurigheid",[150,1208,1210],{"id":1209},"_3-background-processing-voorkomt-cache-misses","3. Background Processing Voorkomt Cache Misses",[34,1212,1213,1216,1219],{},[37,1214,1215],{},"Pre-warm cache voor populaire queries",[37,1217,1218],{},"Draai warming processen tijdens off-peak uren",[37,1220,1221],{},"Monitor cache hit rates en pas strategieën aan",[150,1223,1225],{"id":1224},"_4-cache-duur-optimalisatie","4. Cache Duur Optimalisatie",[34,1227,1228,1231,1234],{},[37,1229,1230],{},"Balanceer versheid met performance",[37,1232,1233],{},"Langere cache tijden voor stabiele data",[37,1235,1236],{},"Kortere cache tijden voor veelvuldig veranderende data",[150,1238,1240],{"id":1239},"_5-monitor-cache-performance","5. Monitor Cache Performance",[34,1242,1243,1246,1249],{},[37,1244,1245],{},"Track cache hit rates",[37,1247,1248],{},"Monitor responsetijden",[37,1250,1251],{},"Pas caching strategieën aan op basis van gebruikspatronen",[19,1253,1255],{"id":1254},"implementatie-checklist","Implementatie Checklist",[24,1257,1258],{},"Als je vergelijkbare paginaload performance problemen hebt:",[34,1260,1263,1276,1285,1294,1303,1312,1321],{"className":1261},[1262],"contains-task-list",[37,1264,1267,1271,1272,1275],{"className":1265},[1266],"task-list-item",[1268,1269],"input",{"disabled":403,"type":1270},"checkbox"," ",[30,1273,1274],{},"Implementeer applicatie-level caching",": Gebruik Redis of vergelijkbaar",[37,1277,1279,1271,1281,1284],{"className":1278},[1266],[1268,1280],{"disabled":403,"type":1270},[30,1282,1283],{},"Voeg database-level caching toe",": Gebruik materialized views voor dure queries",[37,1286,1288,1271,1290,1293],{"className":1287},[1266],[1268,1289],{"disabled":403,"type":1270},[30,1291,1292],{},"Implementeer HTTP response caching",": Cache statische responses",[37,1295,1297,1271,1299,1302],{"className":1296},[1266],[1268,1298],{"disabled":403,"type":1270},[30,1300,1301],{},"Ontwerp cache invalidatie strategie",": Zorg voor data versheid",[37,1304,1306,1271,1308,1311],{"className":1305},[1266],[1268,1307],{"disabled":403,"type":1270},[30,1309,1310],{},"Voeg background cache warming toe",": Pre-warm populaire queries",[37,1313,1315,1271,1317,1320],{"className":1314},[1266],[1268,1316],{"disabled":403,"type":1270},[30,1318,1319],{},"Monitor cache performance",": Track hit rates en responsetijden",[37,1322,1324,1271,1326,1329],{"className":1323},[1266],[1268,1325],{"disabled":403,"type":1270},[30,1327,1328],{},"Optimaliseer cache duur",": Balanceer versheid met performance",[19,1331,1333],{"id":1332},"samenvatting","Samenvatting",[24,1335,1336],{},"Het optimaliseren van paginalaadtijden vereist een uitgebreide caching strategie. Door Redis applicatie caching, database materialized views, HTTP response caching, slimme cache invalidatie en background cache warming te combineren, bereikten we consistente sub-15ms responsetijden voor Rijmwoordenboek.",[24,1338,1339],{},"De sleutel was begrijpen dat caching niet alleen gaat over het opslaan van data—het gaat over het creëren van een multi-layer strategie die bottlenecks op elk niveau elimineert terwijl data versheid behouden blijft.",[24,1341,1342],{},"Als dit artikel je hielp caching optimalisatie te begrijpen, kunnen we je helpen deze technieken te implementeren in je eigen applicaties. Bij Ludulicious specialiseren we ons in:",[34,1344,1345,1351,1357],{},[37,1346,1347,1350],{},[30,1348,1349],{},"Caching Strategieën",": Multi-layer caching oplossingen voor optimale performance",[37,1352,1353,1356],{},[30,1354,1355],{},"Database Performance Optimalisatie",": Van langzame queries tot indexering strategieën",[37,1358,1359,1362],{},[30,1360,1361],{},"Custom Development",": Op maat gemaakte oplossingen voor je specifieke use case",[24,1364,1365],{},[30,1366,1367],{},"Klaar om je paginalaadtijden te optimaliseren?",[24,1369,1370,1375],{},[1371,1372,1374],"a",{"href":1373},"\u002Fcontact","Neem contact op"," voor een gratis consultatie, of bekijk onze andere optimalisatie gidsen:",[34,1377,1378,1384,1390,1396,1402],{},[37,1379,1380],{},[1371,1381,1383],{"href":1382},"\u002Fblog\u002Fpostgresql-performance-strategy","PostgreSQL Performance Tuning: Strategische Lessen uit Productie",[37,1385,1386],{},[1371,1387,1389],{"href":1388},"\u002Fblog\u002Fduikersgids-ruimtelijk-zoeken-optimalisatie","Duikersgids: Hoe Ik Ruimtelijk Zoeken 55x Sneller Maakte",[37,1391,1392],{},[1371,1393,1395],{"href":1394},"\u002Fblog\u002Frijmwoordenboek-fonetische-zoekoptimalisatie","Rijmwoordenboek: Het 3-Seconden Fonetische Zoekprobleem Oplossen",[37,1397,1398],{},[1371,1399,1401],{"href":1400},"\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie","UpstreamAds: Van 1.2s naar 35ms Full-Text Zoeken",[37,1403,1404],{},[1371,1405,1407],{"href":1406},"\u002Fblog\u002Fpostgresql-configuratie-optimalisatie","PostgreSQL Configuratie: De Instellingen Die Ertoe Doen",[1409,1410],"hr",{},[24,1412,1413],{},[1414,1415,1416],"em",{},"Deze optimalisatie case study is gebaseerd op echte productie ervaring met Van Dale Rijmwoordenboek. Alle performance cijfers zijn van echte productie systemen.",[1418,1419,1420],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":60,"searchDepth":74,"depth":74,"links":1422},[1423,1424,1425,1430,1434,1438,1439,1446,1447],{"id":21,"depth":74,"text":22},{"id":121,"depth":74,"text":122},{"id":147,"depth":74,"text":148,"children":1426},[1427,1428,1429],{"id":152,"depth":80,"text":153},{"id":325,"depth":80,"text":326},{"id":490,"depth":80,"text":491},{"id":569,"depth":74,"text":570,"children":1431},[1432,1433],{"id":573,"depth":80,"text":574},{"id":627,"depth":80,"text":628},{"id":775,"depth":74,"text":776,"children":1435},[1436,1437],{"id":779,"depth":80,"text":780},{"id":862,"depth":80,"text":863},{"id":1071,"depth":74,"text":1072},{"id":1175,"depth":74,"text":1176,"children":1440},[1441,1442,1443,1444,1445],{"id":1179,"depth":80,"text":1180},{"id":1194,"depth":80,"text":1195},{"id":1209,"depth":80,"text":1210},{"id":1224,"depth":80,"text":1225},{"id":1239,"depth":80,"text":1240},{"id":1254,"depth":74,"text":1255},{"id":1332,"depth":74,"text":1333},[1449,1349],"Database Optimalisatie","2025-01-17","Leer hoe we Rijmwoordenboek paginalaadtijden optimaliseerden van 100ms+ naar onder 15ms met applicatie-level caching, database query optimalisatie en responsetijd strategieën.","md",{"src":1454},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F9\u002F640\u002F360",{},"\u002Fblog\u002Frijmwoordenboek-caching-optimization",{"title":5,"description":1451},"blog\u002F7.rijmwoordenboek-caching-optimization",[1460,1461,1088,1462,1463,1464,1465],"PostgreSQL","Applicatie Caching","Performance Optimalisatie","Redis","Query Optimalisatie","Rijmwoordenboek","KohO6wzitfvQsJJ8QU8rUSd_bZksKgvDEsTX2LO3Nno",[1468,1471],{"title":1395,"path":1394,"stem":1469,"description":1470,"children":-1},"blog\u002F6.rijmwoordenboek-fonetische-zoekoptimalisatie","Leer hoe we PostgreSQL fonetische zoekopdrachten optimaliseerden voor Van Dale Rijmwoordenboek, waardoor zoektijden daalden van 3.2 seconden naar 85ms met multi-layer indexing strategieën en B-tree deduplicatie.",{"title":1401,"path":1400,"stem":1472,"description":1473,"children":-1},"blog\u002F8.upstreamads-fulltext-zoekoptimalisatie","Leer hoe we PostgreSQL full-text zoeken optimaliseerden voor UpstreamAds, waardoor zoektijden daalden van 1.2 seconden naar 35ms met pre-computed tsvector indexen, multi-taal strategieën en partiële indexering.",[]]