[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-en-\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization-\u002Fen\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization":3,"blog-post-surround-en-\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization-\u002Fen\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization":1052,"related-posts-en-\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization-\u002Fen\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization":1059},{"id":4,"title":5,"authors":6,"badge":13,"body":15,"categories":1002,"date":1005,"description":1006,"extension":1007,"image":1008,"meta":1010,"navigation":159,"path":1041,"readingTime":577,"seo":1042,"stem":1043,"tags":1044,"__hash__":1051},"posts_en\u002Fblog\u002F6.rijmwoordenboek-phonetic-search-optimization.md","Rijmwoordenboek: Solving the 3-Second Phonetic Search Problem",[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 at UpstreamAds and Partner at Ludulicious B.V. with over 20 years of experience in software development, specializing in .NET Core, ServiceStack, C# and database design.",{"label":14},"Phonetic Search",{"type":16,"value":17,"toc":975},"minimark",[18,23,27,33,49,54,114,125,129,132,171,176,190,194,199,202,222,227,247,253,257,260,285,289,306,312,316,319,344,348,365,370,374,378,381,406,410,413,442,446,460,465,469,473,476,511,515,518,593,597,611,616,620,723,727,731,742,746,757,761,772,776,787,791,802,806,809,884,888,891,894,897,917,922,930,962,965,971],[19,20,22],"h2",{"id":21},"the-problem-phonetic-search-too-slow-for-real-time-use","The Problem: Phonetic Search Too Slow for Real-Time Use",[24,25,26],"p",{},"In 2020, Van Dale Rijmwoordenboek faced a critical performance bottleneck. Users searching for rhyming words were waiting 3.2 seconds for results. For a rhyming dictionary, this was completely unacceptable.",[24,28,29],{},[30,31,32],"strong",{},"The Challenge:",[34,35,36,40,43,46],"ul",{},[37,38,39],"li",{},"200,000+ Dutch words with phonetic codes",[37,41,42],{},"Complex phonetic matching algorithms",[37,44,45],{},"Users expecting instant rhyming suggestions",[37,47,48],{},"Similarity calculations killing performance",[24,50,51],{},[30,52,53],{},"The Numbers:",[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","-- This query was taking 3.2+ seconds\nSELECT w1.word, w2.word, \n       similarity(w1.phonetic_code, w2.phonetic_code) as sim\nFROM words w1, words w2 \nWHERE w1.id \u003C w2.id \n  AND similarity(w1.phonetic_code, w2.phonetic_code) > 0.8\nORDER BY sim DESC\nLIMIT 20;\n","sql","",[62,63,64,72,78,84,90,96,102,108],"code",{"__ignoreMap":60},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,71],{},"-- This query was taking 3.2+ seconds\n",[65,73,75],{"class":67,"line":74},2,[65,76,77],{},"SELECT w1.word, w2.word, \n",[65,79,81],{"class":67,"line":80},3,[65,82,83],{},"       similarity(w1.phonetic_code, w2.phonetic_code) as sim\n",[65,85,87],{"class":67,"line":86},4,[65,88,89],{},"FROM words w1, words w2 \n",[65,91,93],{"class":67,"line":92},5,[65,94,95],{},"WHERE w1.id \u003C w2.id \n",[65,97,99],{"class":67,"line":98},6,[65,100,101],{},"  AND similarity(w1.phonetic_code, w2.phonetic_code) > 0.8\n",[65,103,105],{"class":67,"line":104},7,[65,106,107],{},"ORDER BY sim DESC\n",[65,109,111],{"class":67,"line":110},8,[65,112,113],{},"LIMIT 20;\n",[24,115,116],{},[117,118],"img",{"alt":119,"className":120,"height":122,"src":123,"width":124},"Rijmwoordenboek performance monitoring",[121],"rounded-lg",600,"https:\u002F\u002Fpicsum.photos\u002Fid\u002F8\u002F1000\u002F600",1000,[19,126,128],{"id":127},"the-root-cause-missing-phonetic-indexes","The Root Cause: Missing Phonetic Indexes",[24,130,131],{},"The problem was clear from the execution plan:",[55,133,135],{"className":57,"code":134,"language":59,"meta":60,"style":60},"EXPLAIN ANALYZE SELECT w1.word, w2.word, \n       similarity(w1.phonetic_code, w2.phonetic_code) as sim\nFROM words w1, words w2 \nWHERE similarity(w1.phonetic_code, w2.phonetic_code) > 0.8;\n\n-- Result: Nested Loop (cost=0.00..5000000.00 rows=1000000 width=32)\n-- Execution time: 3200.456 ms\n",[62,136,137,142,146,150,155,161,166],{"__ignoreMap":60},[65,138,139],{"class":67,"line":68},[65,140,141],{},"EXPLAIN ANALYZE SELECT w1.word, w2.word, \n",[65,143,144],{"class":67,"line":74},[65,145,83],{},[65,147,148],{"class":67,"line":80},[65,149,89],{},[65,151,152],{"class":67,"line":86},[65,153,154],{},"WHERE similarity(w1.phonetic_code, w2.phonetic_code) > 0.8;\n",[65,156,157],{"class":67,"line":92},[65,158,160],{"emptyLinePlaceholder":159},true,"\n",[65,162,163],{"class":67,"line":98},[65,164,165],{},"-- Result: Nested Loop (cost=0.00..5000000.00 rows=1000000 width=32)\n",[65,167,168],{"class":67,"line":104},[65,169,170],{},"-- Execution time: 3200.456 ms\n",[24,172,173],{},[30,174,175],{},"What was happening:",[34,177,178,181,184,187],{},[37,179,180],{},"PostgreSQL was doing a nested loop join on 200,000+ records",[37,182,183],{},"No indexes existed for phonetic similarity operations",[37,185,186],{},"Every similarity calculation was computed from scratch",[37,188,189],{},"Cartesian product of words was killing performance",[19,191,193],{"id":192},"the-solution-multi-layered-phonetic-indexing","The Solution: Multi-Layered Phonetic Indexing",[195,196,198],"h3",{"id":197},"step-1-create-b-tree-index-for-exact-phonetic-matches","Step 1: Create B-tree Index for Exact Phonetic Matches",[24,200,201],{},"The first breakthrough came with a proper phonetic index:",[55,203,205],{"className":57,"code":204,"language":59,"meta":60,"style":60},"-- Custom phonetic index for exact matches\nCREATE INDEX CONCURRENTLY idx_words_phonetic_btree \nON words USING btree (phonetic_code, frequency DESC);\n",[62,206,207,212,217],{"__ignoreMap":60},[65,208,209],{"class":67,"line":68},[65,210,211],{},"-- Custom phonetic index for exact matches\n",[65,213,214],{"class":67,"line":74},[65,215,216],{},"CREATE INDEX CONCURRENTLY idx_words_phonetic_btree \n",[65,218,219],{"class":67,"line":80},[65,220,221],{},"ON words USING btree (phonetic_code, frequency DESC);\n",[24,223,224],{},[30,225,226],{},"Why This Works:",[34,228,229,235,238,241],{},[37,230,231,234],{},[62,232,233],{},"btree (phonetic_code, frequency DESC)",": B-tree indexes provide fast exact matches and efficient range scans",[37,236,237],{},"Ordered by frequency ensures most common words appear first",[37,239,240],{},"Enables fast lookups for identical phonetic codes",[37,242,243,246],{},[62,244,245],{},"CONCURRENTLY"," allows index creation without blocking writes",[24,248,249,252],{},[30,250,251],{},"Immediate Result:"," Exact phonetic matches dropped from 3.2 seconds to 1.8 seconds (1.8x improvement)",[195,254,256],{"id":255},"step-2-add-trigram-index-for-fuzzy-matching","Step 2: Add Trigram Index for Fuzzy Matching",[24,258,259],{},"For phonetic variations and typos, we added trigram support:",[55,261,263],{"className":57,"code":262,"language":59,"meta":60,"style":60},"-- Trigram index for fuzzy matching\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE INDEX CONCURRENTLY idx_words_trgm \nON words USING gin (word gin_trgm_ops);\n",[62,264,265,270,275,280],{"__ignoreMap":60},[65,266,267],{"class":67,"line":68},[65,268,269],{},"-- Trigram index for fuzzy matching\n",[65,271,272],{"class":67,"line":74},[65,273,274],{},"CREATE EXTENSION IF NOT EXISTS pg_trgm;\n",[65,276,277],{"class":67,"line":80},[65,278,279],{},"CREATE INDEX CONCURRENTLY idx_words_trgm \n",[65,281,282],{"class":67,"line":86},[65,283,284],{},"ON words USING gin (word gin_trgm_ops);\n",[24,286,287],{},[30,288,226],{},[34,290,291,297,300,303],{},[37,292,293,296],{},[62,294,295],{},"gin (word gin_trgm_ops)",": GIN indexes with trigram operators enable fast fuzzy text matching",[37,298,299],{},"Trigram indexes work by breaking words into 3-character substrings",[37,301,302],{},"Makes searches resilient to typos and phonetic variations",[37,304,305],{},"Perfect for finding words with similar phonetic patterns",[24,307,308,311],{},[30,309,310],{},"Result:"," Fuzzy phonetic searches improved to 1.2 seconds (2.7x improvement)",[195,313,315],{"id":314},"step-3-create-partial-index-for-common-words","Step 3: Create Partial Index for Common Words",[24,317,318],{},"Most queries focused on common words, so we created a partial index:",[55,320,322],{"className":57,"code":321,"language":59,"meta":60,"style":60},"-- Partial index for common words (massive performance win)\nCREATE INDEX CONCURRENTLY idx_words_common \nON words (phonetic_code) \nWHERE frequency > 1000;\n",[62,323,324,329,334,339],{"__ignoreMap":60},[65,325,326],{"class":67,"line":68},[65,327,328],{},"-- Partial index for common words (massive performance win)\n",[65,330,331],{"class":67,"line":74},[65,332,333],{},"CREATE INDEX CONCURRENTLY idx_words_common \n",[65,335,336],{"class":67,"line":80},[65,337,338],{},"ON words (phonetic_code) \n",[65,340,341],{"class":67,"line":86},[65,342,343],{},"WHERE frequency > 1000;\n",[24,345,346],{},[30,347,226],{},[34,349,350,356,359,362],{},[37,351,352,355],{},[62,353,354],{},"WHERE frequency > 1000",": Only indexes common words, dramatically reducing index size",[37,357,358],{},"Covers 80% of queries while using only 20% of index space",[37,360,361],{},"Faster index scans and better cache utilization",[37,363,364],{},"Most rhyming searches focus on common words anyway",[24,366,367,369],{},[30,368,310],{}," Common word searches dropped to 600ms (5.3x improvement)",[19,371,373],{"id":372},"the-game-changer-postgresql-14-b-tree-deduplication","The Game Changer: PostgreSQL 14 B-tree Deduplication",[195,375,377],{"id":376},"the-problem-bloated-indexes-with-duplicate-keys","The Problem: Bloated Indexes with Duplicate Keys",[24,379,380],{},"In 2021, we discovered our indexes were bloated with duplicate phonetic codes:",[55,382,384],{"className":57,"code":383,"language":59,"meta":60,"style":60},"-- Before PostgreSQL 14: Each duplicate stored separately\nCREATE INDEX idx_words_old \nON words (phonetic_code, frequency, word);\n-- Result: 450MB index with lots of duplicates\n",[62,385,386,391,396,401],{"__ignoreMap":60},[65,387,388],{"class":67,"line":68},[65,389,390],{},"-- Before PostgreSQL 14: Each duplicate stored separately\n",[65,392,393],{"class":67,"line":74},[65,394,395],{},"CREATE INDEX idx_words_old \n",[65,397,398],{"class":67,"line":80},[65,399,400],{},"ON words (phonetic_code, frequency, word);\n",[65,402,403],{"class":67,"line":86},[65,404,405],{},"-- Result: 450MB index with lots of duplicates\n",[195,407,409],{"id":408},"the-solution-automatic-deduplication","The Solution: Automatic Deduplication",[24,411,412],{},"PostgreSQL 14+ automatic deduplication solved this:",[55,414,416],{"className":57,"code":415,"language":59,"meta":60,"style":60},"-- PostgreSQL 14+: Automatic deduplication\nCREATE INDEX CONCURRENTLY idx_words_dedup \nON words (phonetic_code, frequency, word);\n-- Duplicate key values automatically deduplicated\n-- Result: 180MB index (60% reduction!)\n",[62,417,418,423,428,432,437],{"__ignoreMap":60},[65,419,420],{"class":67,"line":68},[65,421,422],{},"-- PostgreSQL 14+: Automatic deduplication\n",[65,424,425],{"class":67,"line":74},[65,426,427],{},"CREATE INDEX CONCURRENTLY idx_words_dedup \n",[65,429,430],{"class":67,"line":80},[65,431,400],{},[65,433,434],{"class":67,"line":86},[65,435,436],{},"-- Duplicate key values automatically deduplicated\n",[65,438,439],{"class":67,"line":92},[65,440,441],{},"-- Result: 180MB index (60% reduction!)\n",[24,443,444],{},[30,445,226],{},[34,447,448,451,454,457],{},[37,449,450],{},"B-tree indexes traditionally store each duplicate key as a separate entry",[37,452,453],{},"PostgreSQL 14+ recognizes when multiple rows have identical index key values",[37,455,456],{},"Instead of storing \"KAT,1000,cat\" and \"KAT,1000,kat\" separately, it stores the key once with multiple row pointers",[37,458,459],{},"Dramatically reduces index size when you have many duplicate values (common in phonetic codes)",[24,461,462,464],{},[30,463,310],{}," Index size reduced by 60%, query performance improved to 200ms (16x improvement)",[19,466,468],{"id":467},"the-final-optimization-query-rewriting-strategy","The Final Optimization: Query Rewriting Strategy",[195,470,472],{"id":471},"the-problem-inefficient-similarity-calculations","The Problem: Inefficient Similarity Calculations",[24,474,475],{},"The original query was still doing expensive similarity calculations:",[55,477,479],{"className":57,"code":478,"language":59,"meta":60,"style":60},"-- Original query (inefficient)\nSELECT w1.word, w2.word, \n       similarity(w1.phonetic_code, w2.phonetic_code) as sim\nFROM words w1, words w2 \nWHERE w1.id \u003C w2.id \n  AND similarity(w1.phonetic_code, w2.phonetic_code) > 0.8\nORDER BY sim DESC;\n",[62,480,481,486,490,494,498,502,506],{"__ignoreMap":60},[65,482,483],{"class":67,"line":68},[65,484,485],{},"-- Original query (inefficient)\n",[65,487,488],{"class":67,"line":74},[65,489,77],{},[65,491,492],{"class":67,"line":80},[65,493,83],{},[65,495,496],{"class":67,"line":86},[65,497,89],{},[65,499,500],{"class":67,"line":92},[65,501,95],{},[65,503,504],{"class":67,"line":98},[65,505,101],{},[65,507,508],{"class":67,"line":104},[65,509,510],{},"ORDER BY sim DESC;\n",[195,512,514],{"id":513},"the-solution-smart-query-rewriting","The Solution: Smart Query Rewriting",[24,516,517],{},"We rewrote the query to leverage our indexes:",[55,519,521],{"className":57,"code":520,"language":59,"meta":60,"style":60},"-- Rewritten query (much faster)\nWITH phonetic_groups AS (\n    SELECT phonetic_code, array_agg(word ORDER BY frequency DESC) as words\n    FROM words \n    WHERE phonetic_code IS NOT NULL\n    GROUP BY phonetic_code\n    HAVING count(*) > 1\n)\nSELECT unnest(words[1:2]) as word1, \n       unnest(words[2:3]) as word2,\n       1.0 as similarity\nFROM phonetic_groups\nWHERE array_length(words, 1) >= 2;\n",[62,522,523,528,533,538,543,548,553,558,563,569,575,581,587],{"__ignoreMap":60},[65,524,525],{"class":67,"line":68},[65,526,527],{},"-- Rewritten query (much faster)\n",[65,529,530],{"class":67,"line":74},[65,531,532],{},"WITH phonetic_groups AS (\n",[65,534,535],{"class":67,"line":80},[65,536,537],{},"    SELECT phonetic_code, array_agg(word ORDER BY frequency DESC) as words\n",[65,539,540],{"class":67,"line":86},[65,541,542],{},"    FROM words \n",[65,544,545],{"class":67,"line":92},[65,546,547],{},"    WHERE phonetic_code IS NOT NULL\n",[65,549,550],{"class":67,"line":98},[65,551,552],{},"    GROUP BY phonetic_code\n",[65,554,555],{"class":67,"line":104},[65,556,557],{},"    HAVING count(*) > 1\n",[65,559,560],{"class":67,"line":110},[65,561,562],{},")\n",[65,564,566],{"class":67,"line":565},9,[65,567,568],{},"SELECT unnest(words[1:2]) as word1, \n",[65,570,572],{"class":67,"line":571},10,[65,573,574],{},"       unnest(words[2:3]) as word2,\n",[65,576,578],{"class":67,"line":577},11,[65,579,580],{},"       1.0 as similarity\n",[65,582,584],{"class":67,"line":583},12,[65,585,586],{},"FROM phonetic_groups\n",[65,588,590],{"class":67,"line":589},13,[65,591,592],{},"WHERE array_length(words, 1) >= 2;\n",[24,594,595],{},[30,596,226],{},[34,598,599,602,605,608],{},[37,600,601],{},"Groups words by identical phonetic codes first",[37,603,604],{},"Uses array operations instead of expensive similarity calculations",[37,606,607],{},"Leverages our phonetic indexes for fast grouping",[37,609,610],{},"Eliminates the need for cross-joining all words",[24,612,613,615],{},[30,614,310],{}," Query time dropped to 85ms (37x improvement from original)",[19,617,619],{"id":618},"performance-results-summary","Performance Results Summary",[621,622,623,639],"table",{},[624,625,626],"thead",{},[627,628,629,633,636],"tr",{},[630,631,632],"th",{},"Optimization Step",[630,634,635],{},"Query Time",[630,637,638],{},"Improvement",[640,641,642,656,669,682,695,708],"tbody",{},[627,643,644,650,653],{},[645,646,647],"td",{},[30,648,649],{},"Original (No Indexes)",[645,651,652],{},"3,200ms",[645,654,655],{},"Baseline",[627,657,658,663,666],{},[645,659,660],{},[30,661,662],{},"B-tree Phonetic Index",[645,664,665],{},"1,800ms",[645,667,668],{},"1.8x faster",[627,670,671,676,679],{},[645,672,673],{},[30,674,675],{},"Trigram Fuzzy Index",[645,677,678],{},"1,200ms",[645,680,681],{},"2.7x faster",[627,683,684,689,692],{},[645,685,686],{},[30,687,688],{},"Partial Index (Common Words)",[645,690,691],{},"600ms",[645,693,694],{},"5.3x faster",[627,696,697,702,705],{},[645,698,699],{},[30,700,701],{},"B-tree Deduplication",[645,703,704],{},"200ms",[645,706,707],{},"16x faster",[627,709,710,715,718],{},[645,711,712],{},[30,713,714],{},"Query Rewriting",[645,716,717],{},"85ms",[645,719,720],{},[30,721,722],{},"37x faster",[19,724,726],{"id":725},"key-lessons-learned","Key Lessons Learned",[195,728,730],{"id":729},"_1-phonetic-data-requires-specialized-indexes","1. Phonetic Data Requires Specialized Indexes",[34,732,733,736,739],{},[37,734,735],{},"Regular indexes don't work for phonetic similarity",[37,737,738],{},"B-tree indexes excel at exact phonetic matches",[37,740,741],{},"GIN indexes with trigram operators handle fuzzy matching",[195,743,745],{"id":744},"_2-partial-indexes-are-powerful-for-filtered-data","2. Partial Indexes Are Powerful for Filtered Data",[34,747,748,751,754],{},[37,749,750],{},"Only index data you actually query",[37,752,753],{},"Dramatically reduces index size and improves performance",[37,755,756],{},"Perfect for frequency-based filtering",[195,758,760],{"id":759},"_3-b-tree-deduplication-saves-massive-space","3. B-tree Deduplication Saves Massive Space",[34,762,763,766,769],{},[37,764,765],{},"PostgreSQL 14+ automatically deduplicates identical keys",[37,767,768],{},"Essential for data with many duplicate values",[37,770,771],{},"Improves cache utilization and query performance",[195,773,775],{"id":774},"_4-query-rewriting-can-eliminate-expensive-operations","4. Query Rewriting Can Eliminate Expensive Operations",[34,777,778,781,784],{},[37,779,780],{},"Sometimes the best optimization is changing the approach",[37,782,783],{},"Grouping operations can replace expensive similarity calculations",[37,785,786],{},"Leverage indexes to avoid full table scans",[195,788,790],{"id":789},"_5-multi-layered-indexing-strategies-work-best","5. Multi-layered Indexing Strategies Work Best",[34,792,793,796,799],{},[37,794,795],{},"Different index types for different query patterns",[37,797,798],{},"Combine exact matching with fuzzy matching",[37,800,801],{},"Use partial indexes for common query patterns",[19,803,805],{"id":804},"implementation-checklist","Implementation Checklist",[24,807,808],{},"If you're facing similar phonetic search performance issues:",[34,810,813,830,839,848,857,866,875],{"className":811},[812],"contains-task-list",[37,814,817,821,822,825,826,829],{"className":815},[816],"task-list-item",[818,819],"input",{"disabled":159,"type":820},"checkbox"," ",[30,823,824],{},"Analyze your queries",": Use ",[62,827,828],{},"EXPLAIN ANALYZE"," to identify bottlenecks",[37,831,833,821,835,838],{"className":832},[816],[818,834],{"disabled":159,"type":820},[30,836,837],{},"Create B-tree indexes",": For exact phonetic matches",[37,840,842,821,844,847],{"className":841},[816],[818,843],{"disabled":159,"type":820},[30,845,846],{},"Add trigram indexes",": For fuzzy phonetic matching",[37,849,851,821,853,856],{"className":850},[816],[818,852],{"disabled":159,"type":820},[30,854,855],{},"Implement partial indexes",": For commonly filtered data",[37,858,860,821,862,865],{"className":859},[816],[818,861],{"disabled":159,"type":820},[30,863,864],{},"Upgrade to PostgreSQL 14+",": For automatic deduplication",[37,867,869,821,871,874],{"className":868},[816],[818,870],{"disabled":159,"type":820},[30,872,873],{},"Consider query rewriting",": To eliminate expensive operations",[37,876,878,821,880,883],{"className":877},[816],[818,879],{"disabled":159,"type":820},[30,881,882],{},"Monitor index usage",": Track which indexes are actually used",[19,885,887],{"id":886},"summary","Summary",[24,889,890],{},"Optimizing phonetic search in PostgreSQL requires a multi-layered approach. By combining B-tree indexes for exact matches, GIN indexes for fuzzy matching, partial indexing for common words, B-tree deduplication, and smart query rewriting, we achieved a 37x performance improvement for Van Dale Rijmwoordenboek.",[24,892,893],{},"The key was understanding that phonetic data has unique requirements and requires specialized optimization techniques. Generic text search optimization approaches won't work for phonetic similarity operations.",[24,895,896],{},"If this article helped you understand phonetic search optimization, we can help you implement these techniques in your own applications. At Ludulicious, we specialize in:",[34,898,899,905,911],{},[37,900,901,904],{},[30,902,903],{},"Text Search Solutions",": Phonetic matching and similarity algorithms",[37,906,907,910],{},[30,908,909],{},"Database Performance Optimization",": From slow queries to indexing strategies",[37,912,913,916],{},[30,914,915],{},"Custom Development",": Tailored solutions for your specific use case",[24,918,919],{},[30,920,921],{},"Ready to optimize your phonetic search?",[24,923,924,929],{},[925,926,928],"a",{"href":927},"\u002Fcontact","Contact us"," for a free consultation, or check out our other optimization guides:",[34,931,932,938,944,950,956],{},[37,933,934],{},[925,935,937],{"href":936},"\u002Fblog\u002Fpostgresql-performance-strategy","PostgreSQL Performance Tuning: Strategic Lessons from Production",[37,939,940],{},[925,941,943],{"href":942},"\u002Fblog\u002Fduikersgids-spatial-search-optimization","Duikersgids: How I Made Spatial Search 55x Faster",[37,945,946],{},[925,947,949],{"href":948},"\u002Fblog\u002Frijmwoordenboek-caching-optimization","Rijmwoordenboek: Serving Pages Under 15ms with Better Caching",[37,951,952],{},[925,953,955],{"href":954},"\u002Fblog\u002Fupstreamads-fulltext-search-optimization","UpstreamAds: From 1.2s to 35ms Full-Text Search",[37,957,958],{},[925,959,961],{"href":960},"\u002Fblog\u002Fpostgresql-configuration-optimization","PostgreSQL Configuration: The Settings That Matter",[963,964],"hr",{},[24,966,967],{},[968,969,970],"em",{},"This optimization case study is based on real production experience with Van Dale Rijmwoordenboek. All performance numbers are from actual production systems.",[972,973,974],"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":976},[977,978,979,984,988,992,993,1000,1001],{"id":21,"depth":74,"text":22},{"id":127,"depth":74,"text":128},{"id":192,"depth":74,"text":193,"children":980},[981,982,983],{"id":197,"depth":80,"text":198},{"id":255,"depth":80,"text":256},{"id":314,"depth":80,"text":315},{"id":372,"depth":74,"text":373,"children":985},[986,987],{"id":376,"depth":80,"text":377},{"id":408,"depth":80,"text":409},{"id":467,"depth":74,"text":468,"children":989},[990,991],{"id":471,"depth":80,"text":472},{"id":513,"depth":80,"text":514},{"id":618,"depth":74,"text":619},{"id":725,"depth":74,"text":726,"children":994},[995,996,997,998,999],{"id":729,"depth":80,"text":730},{"id":744,"depth":80,"text":745},{"id":759,"depth":80,"text":760},{"id":774,"depth":80,"text":775},{"id":789,"depth":80,"text":790},{"id":804,"depth":74,"text":805},{"id":886,"depth":74,"text":887},[1003,1004],"Database Optimization","Text Search","2025-01-17","Learn how we optimized PostgreSQL phonetic search for Van Dale Rijmwoordenboek, reducing search times from 3.2 seconds to 85ms using multi-layered indexing strategies and B-tree deduplication.","md",{"src":1009},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F8\u002F640\u002F360",{"schema":1011},{"type":1012,"name":5,"description":1013,"image":1009,"author":1014,"datePublished":1005,"dateModified":1005,"publisher":1015,"steps":1018,"totalTime":1037,"estimatedCost":1038},"HowTo","Learn how to optimize PostgreSQL phonetic search queries for rhyming dictionaries, reducing search times from 3.2 seconds to 85ms using multi-layer indexing strategies and B-tree deduplication.",{"name":8,"url":9},{"name":1016,"url":1017},"Ludulicious B.V.","https:\u002F\u002Fludulicious.nl",[1019,1022,1025,1028,1031,1034],{"name":1020,"text":1021},"Analyze Phonetic Search Requirements","Understand the phonetic matching requirements for rhyming word searches",{"name":1023,"text":1024},"Implement Multi-Layer Indexing","Create B-tree and GIN indexes optimized for phonetic text matching",{"name":1026,"text":1027},"Configure B-tree Deduplication","Enable PostgreSQL 13+ B-tree deduplication for better index efficiency",{"name":1029,"text":1030},"Optimize Trigram Search","Implement trigram-based phonetic matching for fuzzy text search",{"name":1032,"text":1033},"Add Incremental Sorting","Use PostgreSQL 13+ incremental sorting for better query performance",{"name":1035,"text":1036},"Test and Validate Results","Verify phonetic search accuracy and measure performance improvements","PT3D",{"currency":1039,"value":1040},"EUR","8000","\u002Fblog\u002Frijmwoordenboek-phonetic-search-optimization",{"title":5,"description":1006},"blog\u002F6.rijmwoordenboek-phonetic-search-optimization",[1045,14,1046,1047,1048,1049,1050],"PostgreSQL","B-tree Indexes","GIN Indexes","Trigram Search","Performance Optimization","Rijmwoordenboek","_3vOSN2meDhPd3Bf9rng0RvBA52o7ooroQ2nCVpJ2Po",[1053,1056],{"title":943,"path":942,"stem":1054,"description":1055,"children":-1},"blog\u002F5.duikersgids-spatial-search-optimization","Learn how we optimized PostgreSQL spatial queries for Duikersgids.nl, reducing search times from 2.5 seconds to 45ms using GiST indexes, partitioning, and parallel processing techniques.",{"title":949,"path":948,"stem":1057,"description":1058,"children":-1},"blog\u002F7.rijmwoordenboek-caching-optimization","Learn how we optimized Rijmwoordenboek page load times from 100ms+ to under 15ms using application-level caching, database query optimization, and response time strategies.",[]]