[{"data":1,"prerenderedAt":341},["ShallowReactive",2],{"nav-stories":3,"footer-stories":61,"project-oak-search":74},[4,16,25,34,43,52],{"id":5,"color":6,"extension":7,"image":8,"label":9,"link":10,"meta":11,"order":12,"stem":13,"text":14,"__hash__":15},"stories\u002Fstories\u002F01-data-center.yml",null,"yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1558494949-ef010cbdcc31?w=1080","DATA_CENTER","https:\u002F\u002Fx.com\u002Fabbeytetteh_",{},1,"stories\u002F01-data-center","Racking new servers. 40gbit backbone online.","0QUZQbaANhdO8WemZxkDdO7vbVopfnynHtH9FxBZb_w",{"id":17,"color":6,"extension":7,"image":18,"label":19,"link":6,"meta":20,"order":21,"stem":22,"text":23,"__hash__":24},"stories\u002Fstories\u002F02-thoughts.yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1498050108023-c5249f4df085?w=1080","THOUGHTS",{},2,"stories\u002F02-thoughts","Late night bug hunting. Found the memory leak.","Gd1am954aasY6HRHD7hCtOuessXb6zYZ8iizS501ICg",{"id":26,"color":27,"extension":7,"image":6,"label":28,"link":6,"meta":29,"order":30,"stem":31,"text":32,"__hash__":33},"stories\u002Fstories\u002F03-coding.yml","#3b82f6","CODING",{},3,"stories\u002F03-coding","Just thinking about how much easier life is with Swarm.","vLAyiGUPtlXB2SHa5KM_U2AaK4QkG3Og85UEUE7qzgM",{"id":35,"color":6,"extension":7,"image":36,"label":37,"link":6,"meta":38,"order":39,"stem":40,"text":41,"__hash__":42},"stories\u002Fstories\u002F04-update.yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1591799264318-7e6ef8ddb7ea?w=1080","UPDATE",{},4,"stories\u002F04-update","New cluster nodes arrived. Prepping for installation.","kyT60N5C6Re_jMonZbgNy0PbQhzXmUWxDbD0D_v43ts",{"id":44,"color":45,"extension":7,"image":6,"label":46,"link":6,"meta":47,"order":48,"stem":49,"text":50,"__hash__":51},"stories\u002Fstories\u002F05-setup.yml","#86868b","SETUP",{},5,"stories\u002F05-setup","Optimizing the telemetry pipeline for 1M req\u002Fs.","cPOBkzoyXsCmPgRO2d80Hj3vm4MP-6nAejtlQ5iuSzw",{"id":53,"color":6,"extension":7,"image":54,"label":55,"link":6,"meta":56,"order":57,"stem":58,"text":59,"__hash__":60},"stories\u002Fstories\u002F06-travel.yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1560969184-10fe8719e047?w=1080","TRAVEL",{},6,"stories\u002F06-travel","Travel log — system architecture workshop in Berlin.","jnOxerdF6usAIHdR35Z-opx0LJAy9kZluXnZhtz62Z0",[62,64,66,68,70,72],{"id":5,"color":6,"extension":7,"image":8,"label":9,"link":10,"meta":63,"order":12,"stem":13,"text":14,"__hash__":15},{},{"id":17,"color":6,"extension":7,"image":18,"label":19,"link":6,"meta":65,"order":21,"stem":22,"text":23,"__hash__":24},{},{"id":26,"color":27,"extension":7,"image":6,"label":28,"link":6,"meta":67,"order":30,"stem":31,"text":32,"__hash__":33},{},{"id":35,"color":6,"extension":7,"image":36,"label":37,"link":6,"meta":69,"order":39,"stem":40,"text":41,"__hash__":42},{},{"id":44,"color":45,"extension":7,"image":6,"label":46,"link":6,"meta":71,"order":48,"stem":49,"text":50,"__hash__":51},{},{"id":53,"color":6,"extension":7,"image":54,"label":55,"link":6,"meta":73,"order":57,"stem":58,"text":59,"__hash__":60},{},{"id":75,"title":76,"body":77,"description":329,"extension":330,"hash":331,"liveUrl":6,"meta":332,"navigation":333,"order":245,"path":334,"rackBay":6,"rackStatus":6,"region":335,"seo":336,"stem":337,"thumbnail":338,"vault":339,"__hash__":340},"projects\u002Fprojects\u002Foak-search.md","Oak Search",{"type":78,"value":79,"toc":323},"minimark",[80,84,89,92,96,299,305,309,312,316,319],[81,82,83],"p",{},"Oak Search is a thin orchestration layer over Elasticsearch that adds per-tenant index isolation, query rewriting for access control, and a structured query DSL consumable from TypeScript.",[85,86,88],"h2",{"id":87},"the-problem-it-solves","The Problem It Solves",[81,90,91],{},"Multi-tenant search has a nasty default: if tenants share an index, a misconfigured query can return documents from the wrong tenant. Oak Search enforces isolation at the query layer — every search request is rewritten to include a tenant filter before hitting Elasticsearch.",[85,93,95],{"id":94},"query-rewriting","Query Rewriting",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"language-typescript shiki shiki-themes vitesse-light","const search = async (tenantId: string, query: SearchQuery) => {\n  const safe: ElasticQuery = {\n    bool: {\n      must: [toElasticQuery(query)],\n      filter: [{ term: { tenant_id: tenantId } }], \u002F\u002F injected, not user-controlled\n    },\n  }\n  return elastic.search({ index: 'documents', body: { query: safe } })\n}\n","typescript","",[104,105,106,159,176,185,204,232,237,243,293],"code",{"__ignoreMap":102},[107,108,110,114,118,122,125,128,132,135,139,142,145,147,150,153,156],"span",{"class":109,"line":12},"line",[107,111,113],{"class":112},"si04Y","const ",[107,115,117],{"class":116},"sySUi","search",[107,119,121],{"class":120},"sYZai"," =",[107,123,124],{"class":112}," async ",[107,126,127],{"class":120},"(",[107,129,131],{"class":130},"svycV","tenantId",[107,133,134],{"class":120},": ",[107,136,138],{"class":137},"sUxyF","string",[107,140,141],{"class":120},",",[107,143,144],{"class":130}," query",[107,146,134],{"class":120},[107,148,149],{"class":137},"SearchQuery",[107,151,152],{"class":120},")",[107,154,155],{"class":120}," =>",[107,157,158],{"class":120}," {\n",[107,160,161,164,167,169,172,174],{"class":109,"line":21},[107,162,163],{"class":112},"  const ",[107,165,166],{"class":130},"safe",[107,168,134],{"class":120},[107,170,171],{"class":137},"ElasticQuery",[107,173,121],{"class":120},[107,175,158],{"class":120},[107,177,178,182],{"class":109,"line":30},[107,179,181],{"class":180},"su6XF","    bool",[107,183,184],{"class":120},": {\n",[107,186,187,190,193,196,198,201],{"class":109,"line":39},[107,188,189],{"class":180},"      must",[107,191,192],{"class":120},": [",[107,194,195],{"class":116},"toElasticQuery",[107,197,127],{"class":120},[107,199,200],{"class":130},"query",[107,202,203],{"class":120},")],\n",[107,205,206,209,212,215,218,221,223,225,228],{"class":109,"line":48},[107,207,208],{"class":180},"      filter",[107,210,211],{"class":120},": [{ ",[107,213,214],{"class":180},"term",[107,216,217],{"class":120},": { ",[107,219,220],{"class":180},"tenant_id",[107,222,134],{"class":120},[107,224,131],{"class":130},[107,226,227],{"class":120}," } }], ",[107,229,231],{"class":230},"s8zF2","\u002F\u002F injected, not user-controlled\n",[107,233,234],{"class":109,"line":57},[107,235,236],{"class":120},"    },\n",[107,238,240],{"class":109,"line":239},7,[107,241,242],{"class":120},"  }\n",[107,244,246,250,253,256,258,261,264,266,270,274,276,279,282,284,286,288,290],{"class":109,"line":245},8,[107,247,249],{"class":248},"sbBg2","  return",[107,251,252],{"class":130}," elastic",[107,254,255],{"class":120},".",[107,257,117],{"class":116},[107,259,260],{"class":120},"({ ",[107,262,263],{"class":180},"index",[107,265,134],{"class":120},[107,267,269],{"class":268},"sSP4y","'",[107,271,273],{"class":272},"spphp","documents",[107,275,269],{"class":268},[107,277,278],{"class":120},", ",[107,280,281],{"class":180},"body",[107,283,217],{"class":120},[107,285,200],{"class":180},[107,287,134],{"class":120},[107,289,166],{"class":130},[107,291,292],{"class":120}," } })\n",[107,294,296],{"class":109,"line":295},9,[107,297,298],{"class":120},"}\n",[81,300,301,302,304],{},"The ",[104,303,220],{}," filter is always injected server-side. The caller has no way to omit or override it.",[85,306,308],{"id":307},"performance","Performance",[81,310,311],{},"With a 10M document corpus split across 8 tenants, median search latency is 42ms. The Elasticsearch cluster uses ILM policies to automatically move older indices to frozen tier storage, keeping hot-tier costs stable as the dataset grows.",[85,313,315],{"id":314},"status","Status",[81,317,318],{},"In production for two tenants. Expanding to a third tenant in the next sprint.",[320,321,322],"style",{},"html pre.shiki code .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}html pre.shiki code .su6XF, html code.shiki .su6XF{--shiki-default:#998418}html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}html pre.shiki code .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}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);}",{"title":102,"searchDepth":21,"depth":21,"links":324},[325,326,327,328],{"id":87,"depth":21,"text":88},{"id":94,"depth":21,"text":95},{"id":307,"depth":21,"text":308},{"id":314,"depth":21,"text":315},"Distributed search engine layer built on top of Elasticsearch, providing sub-100ms full-text search across multi-tenant document stores.","md","V2W3X4",{},true,"\u002Fprojects\u002Foak-search","AP-SOUTH-1",{"title":76,"description":329},"projects\u002Foak-search","\u002Fimages\u002Fthumbnails\u002Foak-search.png",false,"Xpd7AeVt0jL-b_KJf6o-RcvzbbsmSsfy-GijgtpMNDA",1779361989376]