[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-nl-\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie-\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie":3,"blog-post-surround-nl-\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie-\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie":1065,"related-posts-nl-\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie-\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie":1076},{"id":4,"title":5,"authors":6,"badge":13,"body":15,"categories":1046,"date":1048,"description":1049,"extension":1050,"image":1051,"meta":1053,"navigation":179,"path":1054,"readingTime":104,"seo":1055,"stem":1056,"tags":1057,"__hash__":1064},"posts_nl\u002Fblog\u002F8.upstreamads-fulltext-zoekoptimalisatie.md","UpstreamAds: Van 1.2s naar 35ms Full-Text Zoeken",[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},"Full-Text Zoeken",{"type":16,"value":17,"toc":1019},"minimark",[18,23,27,33,49,54,126,137,141,144,191,196,213,217,222,225,245,250,270,276,280,283,318,322,342,348,352,355,380,384,401,406,410,414,417,447,451,454,512,516,533,538,542,546,549,588,592,595,632,636,655,660,664,765,769,773,784,788,799,803,814,818,829,833,844,848,851,922,926,929,932,935,955,960,968,1006,1009,1015],[19,20,22],"h2",{"id":21},"het-probleem-full-text-zoeken-bottleneck-voor-ons-platform","Het Probleem: Full-Text Zoeken Bottleneck voor Ons Platform",[24,25,26],"p",{},"In 2022 had UpstreamAds te maken met een kritiek performance crisis. Onze full-text zoekfunctie deed er 1.2 seconden over per query, wat een bottleneck vormde voor ons hele advertentieplatform. Adverteerders gaven hun zoekopdrachten op, en we verloren omzet.",[24,28,29],{},[30,31,32],"strong",{},"De Uitdaging:",[34,35,36,40,43,46],"ul",{},[37,38,39],"li",{},"Miljoenen ad creatives die multi-taal zoeken vereisen",[37,41,42],{},"Adverteerders die instant campagne-suggesties verwachten",[37,44,45],{},"Full-text zoeken die tekst verwerkt tijdens query tijd",[37,47,48],{},"Geen optimalisatie voor veelgebruikte zoekpatronen",[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","-- Deze query deed er 1.2+ seconden over\nSELECT ac.id, ac.title, ac.description,\n       ts_rank(to_tsvector('english', ac.title || ' ' || ac.description), \n               plainto_tsquery('english', 'diving equipment')) as rank\nFROM ad_creatives ac\nWHERE to_tsvector('english', ac.title || ' ' || ac.description) \n      @@ plainto_tsquery('english', 'diving equipment')\n  AND ac.status = 'active'\nORDER BY rank DESC\nLIMIT 20;\n","sql","",[62,63,64,72,78,84,90,96,102,108,114,120],"code",{"__ignoreMap":60},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,71],{},"-- Deze query deed er 1.2+ seconden over\n",[65,73,75],{"class":67,"line":74},2,[65,76,77],{},"SELECT ac.id, ac.title, ac.description,\n",[65,79,81],{"class":67,"line":80},3,[65,82,83],{},"       ts_rank(to_tsvector('english', ac.title || ' ' || ac.description), \n",[65,85,87],{"class":67,"line":86},4,[65,88,89],{},"               plainto_tsquery('english', 'diving equipment')) as rank\n",[65,91,93],{"class":67,"line":92},5,[65,94,95],{},"FROM ad_creatives ac\n",[65,97,99],{"class":67,"line":98},6,[65,100,101],{},"WHERE to_tsvector('english', ac.title || ' ' || ac.description) \n",[65,103,105],{"class":67,"line":104},7,[65,106,107],{},"      @@ plainto_tsquery('english', 'diving equipment')\n",[65,109,111],{"class":67,"line":110},8,[65,112,113],{},"  AND ac.status = 'active'\n",[65,115,117],{"class":67,"line":116},9,[65,118,119],{},"ORDER BY rank DESC\n",[65,121,123],{"class":67,"line":122},10,[65,124,125],{},"LIMIT 20;\n",[24,127,128],{},[129,130],"img",{"alt":131,"className":132,"height":134,"src":135,"width":136},"UpstreamAds full-text zoeken performance",[133],"rounded-lg",600,"https:\u002F\u002Fpicsum.photos\u002Fid\u002F10\u002F1000\u002F600",1000,[19,138,140],{"id":139},"de-oorzaak-runtime-tekst-verwerking","De Oorzaak: Runtime Tekst Verwerking",[24,142,143],{},"Het probleem was duidelijk uit het execution plan:",[55,145,147],{"className":57,"code":146,"language":59,"meta":60,"style":60},"EXPLAIN ANALYZE SELECT ac.id, ac.title, ac.description,\n       ts_rank(to_tsvector('english', ac.title || ' ' || ac.description), \n               plainto_tsquery('english', 'diving equipment')) as rank\nFROM ad_creatives ac\nWHERE to_tsvector('english', ac.title || ' ' || ac.description) \n      @@ plainto_tsquery('english', 'diving equipment');\n\n-- Resultaat: Seq Scan on ad_creatives (cost=0.00..50000.00 rows=1000000 width=64)\n-- Execution time: 1200.456 ms\n",[62,148,149,154,158,162,166,170,175,181,186],{"__ignoreMap":60},[65,150,151],{"class":67,"line":68},[65,152,153],{},"EXPLAIN ANALYZE SELECT ac.id, ac.title, ac.description,\n",[65,155,156],{"class":67,"line":74},[65,157,83],{},[65,159,160],{"class":67,"line":80},[65,161,89],{},[65,163,164],{"class":67,"line":86},[65,165,95],{},[65,167,168],{"class":67,"line":92},[65,169,101],{},[65,171,172],{"class":67,"line":98},[65,173,174],{},"      @@ plainto_tsquery('english', 'diving equipment');\n",[65,176,177],{"class":67,"line":104},[65,178,180],{"emptyLinePlaceholder":179},true,"\n",[65,182,183],{"class":67,"line":110},[65,184,185],{},"-- Resultaat: Seq Scan on ad_creatives (cost=0.00..50000.00 rows=1000000 width=64)\n",[65,187,188],{"class":67,"line":116},[65,189,190],{},"-- Execution time: 1200.456 ms\n",[24,192,193],{},[30,194,195],{},"Wat er gebeurde:",[34,197,198,204,207,210],{},[37,199,200,201],{},"PostgreSQL verwerkte tekst tijdens query tijd met ",[62,202,203],{},"to_tsvector()",[37,205,206],{},"Er bestonden geen pre-computed search vectors",[37,208,209],{},"Volledige tabel scan op miljoenen ad creatives",[37,211,212],{},"Tekst verwerking was CPU-intensief en traag",[19,214,216],{"id":215},"de-oplossing-pre-computed-search-vectors","De Oplossing: Pre-Computed Search Vectors",[218,219,221],"h3",{"id":220},"stap-1-maak-pre-computed-tsvector-index","Stap 1: Maak Pre-Computed tsvector Index",[24,223,224],{},"De eerste doorbraak kwam met pre-computed search vectors:",[55,226,228],{"className":57,"code":227,"language":59,"meta":60,"style":60},"-- Pre-computed full-text zoek index\nCREATE INDEX CONCURRENTLY idx_ads_fts \nON ad_creatives USING gin (to_tsvector('english', title || ' ' || description));\n",[62,229,230,235,240],{"__ignoreMap":60},[65,231,232],{"class":67,"line":68},[65,233,234],{},"-- Pre-computed full-text zoek index\n",[65,236,237],{"class":67,"line":74},[65,238,239],{},"CREATE INDEX CONCURRENTLY idx_ads_fts \n",[65,241,242],{"class":67,"line":80},[65,243,244],{},"ON ad_creatives USING gin (to_tsvector('english', title || ' ' || description));\n",[24,246,247],{},[30,248,249],{},"Waarom Dit Werkt:",[34,251,252,258,261,267],{},[37,253,254,257],{},[62,255,256],{},"gin (to_tsvector(...))",": GIN indexen slaan pre-computed full-text search vectors op",[37,259,260],{},"Elimineert dure tekst verwerking tijdens queries",[37,262,263,266],{},[62,264,265],{},"to_tsvector('english', ...)",": Converteert tekst naar doorzoekbare tokens met Engelse taalregels",[37,268,269],{},"Pre-computed vectors betekenen dat queries geen tekst hoeven te parsen en tokenizen tijdens runtime",[24,271,272,275],{},[30,273,274],{},"Direct Resultaat:"," Query tijd daalde van 1.2 seconden naar 400ms (3x verbetering)",[218,277,279],{"id":278},"stap-2-voeg-multi-taal-ondersteuning-toe","Stap 2: Voeg Multi-Taal Ondersteuning Toe",[24,281,282],{},"Voor internationale campagnes voegden we multi-taal zoeken toe:",[55,284,286],{"className":57,"code":285,"language":59,"meta":60,"style":60},"-- Multi-taal ondersteuning voor internationale campagnes\nCREATE INDEX CONCURRENTLY idx_ads_fts_multilang \nON ad_creatives USING gin (\n    to_tsvector('english', title || ' ' || description) ||\n    to_tsvector('dutch', title || ' ' || description)\n);\n",[62,287,288,293,298,303,308,313],{"__ignoreMap":60},[65,289,290],{"class":67,"line":68},[65,291,292],{},"-- Multi-taal ondersteuning voor internationale campagnes\n",[65,294,295],{"class":67,"line":74},[65,296,297],{},"CREATE INDEX CONCURRENTLY idx_ads_fts_multilang \n",[65,299,300],{"class":67,"line":80},[65,301,302],{},"ON ad_creatives USING gin (\n",[65,304,305],{"class":67,"line":86},[65,306,307],{},"    to_tsvector('english', title || ' ' || description) ||\n",[65,309,310],{"class":67,"line":92},[65,311,312],{},"    to_tsvector('dutch', title || ' ' || description)\n",[65,314,315],{"class":67,"line":98},[65,316,317],{},");\n",[24,319,320],{},[30,321,249],{},[34,323,324,330,333,339],{},[37,325,326,329],{},[62,327,328],{},"||"," operator: Concateneert meerdere tsvector kolommen",[37,331,332],{},"Maakt zoeken over meerdere talen tegelijk mogelijk",[37,334,335,338],{},[62,336,337],{},"to_tsvector('dutch', ...)",": Gebruikt Nederlandse taalregels voor stemming en stop woorden",[37,340,341],{},"Enkele index handelt zowel Engelse als Nederlandse zoekopdrachten efficiënt af",[24,343,344,347],{},[30,345,346],{},"Resultaat:"," Multi-taal zoekopdrachten verbeterden naar 200ms (6x verbetering)",[218,349,351],{"id":350},"stap-3-maak-partiële-index-voor-actieve-ads","Stap 3: Maak Partiële Index voor Actieve Ads",[24,353,354],{},"De meeste queries hadden alleen actieve ad creatives nodig, dus maakten we een partiële index:",[55,356,358],{"className":57,"code":357,"language":59,"meta":60,"style":60},"-- Partiële index voor alleen actieve ads\nCREATE INDEX CONCURRENTLY idx_ads_active_fts \nON ad_creatives USING gin (to_tsvector('english', title)) \nWHERE status = 'active' AND created_at > '2023-01-01';\n",[62,359,360,365,370,375],{"__ignoreMap":60},[65,361,362],{"class":67,"line":68},[65,363,364],{},"-- Partiële index voor alleen actieve ads\n",[65,366,367],{"class":67,"line":74},[65,368,369],{},"CREATE INDEX CONCURRENTLY idx_ads_active_fts \n",[65,371,372],{"class":67,"line":80},[65,373,374],{},"ON ad_creatives USING gin (to_tsvector('english', title)) \n",[65,376,377],{"class":67,"line":86},[65,378,379],{},"WHERE status = 'active' AND created_at > '2023-01-01';\n",[24,381,382],{},[30,383,249],{},[34,385,386,392,395,398],{},[37,387,388,391],{},[62,389,390],{},"WHERE status = 'active'",": Indexeert alleen actieve ad creatives",[37,393,394],{},"Dramatisch kleinere index grootte (alleen ~2M actieve ads vs 5M+ totaal)",[37,396,397],{},"Snellere index scans en betere cache benutting",[37,399,400],{},"De meeste zoekopdrachten focussen toch op actieve campagnes",[24,402,403,405],{},[30,404,346],{}," Actieve ad zoekopdrachten daalden naar 100ms (12x verbetering)",[19,407,409],{"id":408},"de-game-changer-generated-columns","De Game Changer: Generated Columns",[218,411,413],{"id":412},"het-probleem-index-onderhoud-overhead","Het Probleem: Index Onderhoud Overhead",[24,415,416],{},"Met pre-computed vectors hadden we te maken met index onderhoud problemen:",[55,418,420],{"className":57,"code":419,"language":59,"meta":60,"style":60},"-- Probleem: Index moet herbouwd worden wanneer data verandert\nUPDATE ad_creatives \nSET title = 'New Diving Equipment Campaign'\nWHERE id = 12345;\n-- Index moet geüpdatet worden met nieuwe tsvector\n",[62,421,422,427,432,437,442],{"__ignoreMap":60},[65,423,424],{"class":67,"line":68},[65,425,426],{},"-- Probleem: Index moet herbouwd worden wanneer data verandert\n",[65,428,429],{"class":67,"line":74},[65,430,431],{},"UPDATE ad_creatives \n",[65,433,434],{"class":67,"line":80},[65,435,436],{},"SET title = 'New Diving Equipment Campaign'\n",[65,438,439],{"class":67,"line":86},[65,440,441],{},"WHERE id = 12345;\n",[65,443,444],{"class":67,"line":92},[65,445,446],{},"-- Index moet geüpdatet worden met nieuwe tsvector\n",[218,448,450],{"id":449},"de-oplossing-postgresql-12-generated-columns","De Oplossing: PostgreSQL 12+ Generated Columns",[24,452,453],{},"PostgreSQL 12+ generated columns loste dit op:",[55,455,457],{"className":57,"code":456,"language":59,"meta":60,"style":60},"-- Voeg generated column toe voor zoek optimalisatie\nALTER TABLE ad_creatives \nADD COLUMN search_vector tsvector \nGENERATED ALWAYS AS (\n    to_tsvector('english', title || ' ' || description) ||\n    to_tsvector('dutch', title || ' ' || description)\n) STORED;\n\n-- Maak index op generated column\nCREATE INDEX CONCURRENTLY idx_ads_generated_fts \nON ad_creatives USING gin (search_vector);\n",[62,458,459,464,469,474,479,483,487,492,496,501,506],{"__ignoreMap":60},[65,460,461],{"class":67,"line":68},[65,462,463],{},"-- Voeg generated column toe voor zoek optimalisatie\n",[65,465,466],{"class":67,"line":74},[65,467,468],{},"ALTER TABLE ad_creatives \n",[65,470,471],{"class":67,"line":80},[65,472,473],{},"ADD COLUMN search_vector tsvector \n",[65,475,476],{"class":67,"line":86},[65,477,478],{},"GENERATED ALWAYS AS (\n",[65,480,481],{"class":67,"line":92},[65,482,307],{},[65,484,485],{"class":67,"line":98},[65,486,312],{},[65,488,489],{"class":67,"line":104},[65,490,491],{},") STORED;\n",[65,493,494],{"class":67,"line":110},[65,495,180],{"emptyLinePlaceholder":179},[65,497,498],{"class":67,"line":116},[65,499,500],{},"-- Maak index op generated column\n",[65,502,503],{"class":67,"line":122},[65,504,505],{},"CREATE INDEX CONCURRENTLY idx_ads_generated_fts \n",[65,507,509],{"class":67,"line":508},11,[65,510,511],{},"ON ad_creatives USING gin (search_vector);\n",[24,513,514],{},[30,515,249],{},[34,517,518,524,527,530],{},[37,519,520,523],{},[62,521,522],{},"GENERATED ALWAYS AS (...)"," STORED: Berekent automatisch tsvector wanneer data verandert",[37,525,526],{},"Geen handmatig index onderhoud vereist",[37,528,529],{},"PostgreSQL updateert automatisch de generated column",[37,531,532],{},"Index blijft automatisch gesynchroniseerd met data",[24,534,535,537],{},[30,536,346],{}," Index onderhoud geëlimineerd, query performance behouden op 100ms",[19,539,541],{"id":540},"de-finale-optimalisatie-query-herschrijf-strategie","De Finale Optimalisatie: Query Herschrijf Strategie",[218,543,545],{"id":544},"het-probleem-complexe-ranking-berekeningen","Het Probleem: Complexe Ranking Berekeningen",[24,547,548],{},"De originele query deed nog steeds dure ranking berekeningen:",[55,550,552],{"className":57,"code":551,"language":59,"meta":60,"style":60},"-- Originele query (nog steeds traag)\nSELECT ac.id, ac.title, ac.description,\n       ts_rank(to_tsvector('english', ac.title || ' ' || ac.description), \n               plainto_tsquery('english', 'diving equipment')) as rank\nFROM ad_creatives ac\nWHERE to_tsvector('english', ac.title || ' ' || ac.description) \n      @@ plainto_tsquery('english', 'diving equipment')\nORDER BY rank DESC;\n",[62,553,554,559,563,567,571,575,579,583],{"__ignoreMap":60},[65,555,556],{"class":67,"line":68},[65,557,558],{},"-- Originele query (nog steeds traag)\n",[65,560,561],{"class":67,"line":74},[65,562,77],{},[65,564,565],{"class":67,"line":80},[65,566,83],{},[65,568,569],{"class":67,"line":86},[65,570,89],{},[65,572,573],{"class":67,"line":92},[65,574,95],{},[65,576,577],{"class":67,"line":98},[65,578,101],{},[65,580,581],{"class":67,"line":104},[65,582,107],{},[65,584,585],{"class":67,"line":110},[65,586,587],{},"ORDER BY rank DESC;\n",[218,589,591],{"id":590},"de-oplossing-pre-computed-ranking","De Oplossing: Pre-Computed Ranking",[24,593,594],{},"We herschreven de query om onze generated column te gebruiken:",[55,596,598],{"className":57,"code":597,"language":59,"meta":60,"style":60},"-- Herschreven query (veel sneller)\nSELECT ac.id, ac.title, ac.description, ac.search_rank\nFROM ad_creatives ac\nWHERE ac.search_vector @@ plainto_tsquery('english', 'diving equipment')\n  AND ac.status = 'active'\nORDER BY ac.search_rank DESC\nLIMIT 20;\n",[62,599,600,605,610,614,619,623,628],{"__ignoreMap":60},[65,601,602],{"class":67,"line":68},[65,603,604],{},"-- Herschreven query (veel sneller)\n",[65,606,607],{"class":67,"line":74},[65,608,609],{},"SELECT ac.id, ac.title, ac.description, ac.search_rank\n",[65,611,612],{"class":67,"line":80},[65,613,95],{},[65,615,616],{"class":67,"line":86},[65,617,618],{},"WHERE ac.search_vector @@ plainto_tsquery('english', 'diving equipment')\n",[65,620,621],{"class":67,"line":92},[65,622,113],{},[65,624,625],{"class":67,"line":98},[65,626,627],{},"ORDER BY ac.search_rank DESC\n",[65,629,630],{"class":67,"line":104},[65,631,125],{},[24,633,634],{},[30,635,249],{},[34,637,638,647,649,652],{},[37,639,640,641,644,645],{},"Gebruikt pre-computed ",[62,642,643],{},"search_vector"," in plaats van runtime ",[62,646,203],{},[37,648,260],{},[37,650,651],{},"Benut onze GIN index voor snelle full-text matching",[37,653,654],{},"Pre-computed ranking elimineert runtime berekeningen",[24,656,657,659],{},[30,658,346],{}," Query tijd daalde naar 35ms (34x verbetering van origineel)",[19,661,663],{"id":662},"performance-resultaten-samenvatting","Performance Resultaten Samenvatting",[665,666,667,683],"table",{},[668,669,670],"thead",{},[671,672,673,677,680],"tr",{},[674,675,676],"th",{},"Optimalisatie Stap",[674,678,679],{},"Query Tijd",[674,681,682],{},"Verbetering",[684,685,686,700,713,726,739,750],"tbody",{},[671,687,688,694,697],{},[689,690,691],"td",{},[30,692,693],{},"Origineel (Runtime Verwerking)",[689,695,696],{},"1.200ms",[689,698,699],{},"Baseline",[671,701,702,707,710],{},[689,703,704],{},[30,705,706],{},"Pre-Computed tsvector Index",[689,708,709],{},"400ms",[689,711,712],{},"3x sneller",[671,714,715,720,723],{},[689,716,717],{},[30,718,719],{},"Multi-Taal Ondersteuning",[689,721,722],{},"200ms",[689,724,725],{},"6x sneller",[671,727,728,733,736],{},[689,729,730],{},[30,731,732],{},"Partiële Index (Actieve Ads)",[689,734,735],{},"100ms",[689,737,738],{},"12x sneller",[671,740,741,746,748],{},[689,742,743],{},[30,744,745],{},"Generated Columns",[689,747,735],{},[689,749,738],{},[671,751,752,757,760],{},[689,753,754],{},[30,755,756],{},"Query Herschrijving",[689,758,759],{},"35ms",[689,761,762],{},[30,763,764],{},"34x sneller",[19,766,768],{"id":767},"belangrijke-lessen-geleerd","Belangrijke Lessen Geleerd",[218,770,772],{"id":771},"_1-pre-computed-vectors-zijn-essentieel","1. Pre-Computed Vectors Zijn Essentieel",[34,774,775,778,781],{},[37,776,777],{},"Runtime tekst verwerking is duur",[37,779,780],{},"GIN indexen met tsvector bieden snelle full-text zoekfunctie",[37,782,783],{},"Pre-computatie elimineert query-tijd verwerking overhead",[218,785,787],{"id":786},"_2-multi-taal-ondersteuning-vereist-planning","2. Multi-Taal Ondersteuning Vereist Planning",[34,789,790,793,796],{},[37,791,792],{},"Concateneer meerdere taal tsvectors",[37,794,795],{},"Gebruik geschikte taal configuraties",[37,797,798],{},"Overweeg zoekpatronen over talen",[218,800,802],{"id":801},"_3-partiële-indexen-optimaliseren-veelgebruikte-queries","3. Partiële Indexen Optimaliseren Veelgebruikte Queries",[34,804,805,808,811],{},[37,806,807],{},"Indexeer alleen data die je daadwerkelijk zoekt",[37,809,810],{},"Dramatisch kleinere index grootte en betere performance",[37,812,813],{},"Perfect voor gefilterde datasets",[218,815,817],{"id":816},"_4-generated-columns-elimineren-onderhoud","4. Generated Columns Elimineren Onderhoud",[34,819,820,823,826],{},[37,821,822],{},"PostgreSQL 12+ onderhoudt automatisch generated columns",[37,824,825],{},"Geen handmatige index updates vereist",[37,827,828],{},"Blijft gesynchroniseerd met data wijzigingen",[218,830,832],{"id":831},"_5-query-herschrijving-kan-dure-operaties-elimineren","5. Query Herschrijving Kan Dure Operaties Elimineren",[34,834,835,838,841],{},[37,836,837],{},"Gebruik pre-computed data in plaats van runtime berekeningen",[37,839,840],{},"Benut indexen om volledige tabel scans te vermijden",[37,842,843],{},"Optimaliseer voor veelgebruikte query patronen",[19,845,847],{"id":846},"implementatie-checklist","Implementatie Checklist",[24,849,850],{},"Als je vergelijkbare full-text zoekopdracht performance problemen hebt:",[34,852,855,868,877,886,895,904,913],{"className":853},[854],"contains-task-list",[37,856,859,863,864,867],{"className":857},[858],"task-list-item",[860,861],"input",{"disabled":179,"type":862},"checkbox"," ",[30,865,866],{},"Maak pre-computed tsvector indexen",": Gebruik GIN indexen met tsvector",[37,869,871,863,873,876],{"className":870},[858],[860,872],{"disabled":179,"type":862},[30,874,875],{},"Voeg multi-taal ondersteuning toe",": Concateneer meerdere taal vectors",[37,878,880,863,882,885],{"className":879},[858],[860,881],{"disabled":179,"type":862},[30,883,884],{},"Implementeer partiële indexen",": Voor veelgebruikte gefilterde data",[37,887,889,863,891,894],{"className":888},[858],[860,890],{"disabled":179,"type":862},[30,892,893],{},"Gebruik generated columns",": Voor automatisch onderhoud (PostgreSQL 12+)",[37,896,898,863,900,903],{"className":897},[858],[860,899],{"disabled":179,"type":862},[30,901,902],{},"Herschrijf queries",": Om pre-computed data te gebruiken",[37,905,907,863,909,912],{"className":906},[858],[860,908],{"disabled":179,"type":862},[30,910,911],{},"Monitor index gebruik",": Volg welke indexen daadwerkelijk gebruikt worden",[37,914,916,863,918,921],{"className":915},[858],[860,917],{"disabled":179,"type":862},[30,919,920],{},"Optimaliseer taal configuraties",": Voor je specifieke use case",[19,923,925],{"id":924},"samenvatting","Samenvatting",[24,927,928],{},"Het optimaliseren van full-text zoeken in PostgreSQL vereist het verplaatsen van runtime verwerking naar pre-computed vectors. Door GIN indexen met tsvector, multi-taal ondersteuning, partiële indexering, generated columns en query herschrijving te combineren, behaalden we een 34x performance verbetering voor UpstreamAds.",[24,930,931],{},"De sleutel was het begrijpen dat full-text zoekoptimalisatie niet alleen gaat over indexen—het gaat over het elimineren van dure operaties tijdens query tijd door pre-computatie en slimme query design.",[24,933,934],{},"Als dit artikel je hielp full-text zoekoptimalisatie te begrijpen, kunnen we je helpen deze technieken te implementeren in je eigen applicaties. Bij Ludulicious specialiseren we ons in:",[34,936,937,943,949],{},[37,938,939,942],{},[30,940,941],{},"Full-Text Zoekoplossingen",": Multi-taal zoekoptimalisatie",[37,944,945,948],{},[30,946,947],{},"Database Performance Optimalisatie",": Van langzame queries tot indexing strategieën",[37,950,951,954],{},[30,952,953],{},"Custom Development",": Op maat gemaakte oplossingen voor je specifieke use case",[24,956,957],{},[30,958,959],{},"Klaar om je full-text zoekfunctie te optimaliseren?",[24,961,962,967],{},[963,964,966],"a",{"href":965},"\u002Fcontact","Neem contact op"," voor een gratis consultatie, of bekijk onze andere optimalisatie gidsen:",[34,969,970,976,982,988,994,1000],{},[37,971,972],{},[963,973,975],{"href":974},"\u002Fblog\u002Fpostgresql-performance-strategy","PostgreSQL Performance Tuning: Strategische Lessen uit Productie",[37,977,978],{},[963,979,981],{"href":980},"\u002Fblog\u002Fduikersgids-ruimtelijk-zoeken-optimalisatie","Duikersgids: Hoe Ik Ruimtelijk Zoeken 55x Sneller Maakte",[37,983,984],{},[963,985,987],{"href":986},"\u002Fblog\u002Frijmwoordenboek-fonetische-zoekoptimalisatie","Rijmwoordenboek: Het 3-Seconden Fonetische Zoekprobleem Oplossen",[37,989,990],{},[963,991,993],{"href":992},"\u002Fblog\u002Frijmwoordenboek-caching-optimalisatie","Rijmwoordenboek: Pagina's Serveren Onder 15ms met Betere Caching",[37,995,996],{},[963,997,999],{"href":998},"\u002Fblog\u002Fupstreamads-wal-optimalisatie","UpstreamAds: Write Performance Oplossen met WAL Optimalisatie",[37,1001,1002],{},[963,1003,1005],{"href":1004},"\u002Fblog\u002Fpostgresql-configuratie-optimalisatie","PostgreSQL Configuratie: De Instellingen Die Ertoe Doen",[1007,1008],"hr",{},[24,1010,1011],{},[1012,1013,1014],"em",{},"Deze optimalisatie case study is gebaseerd op echte productie ervaring met UpstreamAds. Alle performance nummers komen van echte productie systemen.",[1016,1017,1018],"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":1020},[1021,1022,1023,1028,1032,1036,1037,1044,1045],{"id":21,"depth":74,"text":22},{"id":139,"depth":74,"text":140},{"id":215,"depth":74,"text":216,"children":1024},[1025,1026,1027],{"id":220,"depth":80,"text":221},{"id":278,"depth":80,"text":279},{"id":350,"depth":80,"text":351},{"id":408,"depth":74,"text":409,"children":1029},[1030,1031],{"id":412,"depth":80,"text":413},{"id":449,"depth":80,"text":450},{"id":540,"depth":74,"text":541,"children":1033},[1034,1035],{"id":544,"depth":80,"text":545},{"id":590,"depth":80,"text":591},{"id":662,"depth":74,"text":663},{"id":767,"depth":74,"text":768,"children":1038},[1039,1040,1041,1042,1043],{"id":771,"depth":80,"text":772},{"id":786,"depth":80,"text":787},{"id":801,"depth":80,"text":802},{"id":816,"depth":80,"text":817},{"id":831,"depth":80,"text":832},{"id":846,"depth":74,"text":847},{"id":924,"depth":74,"text":925},[1047,14],"Database Optimalisatie","2025-01-17","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.","md",{"src":1052},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F10\u002F640\u002F360",{},"\u002Fblog\u002Fupstreamads-fulltext-zoekoptimalisatie",{"title":5,"description":1049},"blog\u002F8.upstreamads-fulltext-zoekoptimalisatie",[1058,14,1059,1060,1061,1062,1063],"PostgreSQL","tsvector","GIN Indexen","Multi-taal Zoeken","Performance Optimalisatie","UpstreamAds","XVK1-89TZh513FebQKsia4mnvTONANPeY11wVOKv52g",[1066,1071],{"title":1067,"path":1068,"stem":1069,"description":1070,"children":-1},"Rijmwoordenboek: Pagina's Onder 15ms Met Betere Caching","\u002Fblog\u002Frijmwoordenboek-caching-optimization","blog\u002F7.rijmwoordenboek-caching-optimization","Leer hoe we Rijmwoordenboek paginalaadtijden optimaliseerden van 100ms+ naar onder 15ms met applicatie-level caching, database query optimalisatie en responsetijd strategieën.",{"title":1072,"path":1073,"stem":1074,"description":1075,"children":-1},"UpstreamAds: Write Performance Oplossen Met WAL Optimalisatie","\u002Fblog\u002Fupstreamads-wal-optimization","blog\u002F9.upstreamads-wal-optimization","Leer hoe we PostgreSQL write performance optimaliseerden voor UpstreamAds, waarbij we ad creative save tijden verbeterden van 500ms naar 100ms met WAL configuratie, hardware optimalisatie en connection pooling strategieën.",[]]