[{"data":1,"prerenderedAt":1193},["ShallowReactive",2],{"navigation_docs":3,"-docs-architecture-caching":158,"-docs-architecture-caching-surround":1188},[4,15,74,98,113],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":6},"Getting Started",false,"\u002Fdocs\u002Fgetting-started","docs\u002F1.getting-started",[10],{"title":11,"path":12,"stem":13,"icon":14},"Zeeq Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"children":19,"page":6},"Key Features","\u002Fdocs\u002Fkey-features","docs\u002F3.key-features",[20,25,30,35,40,45,50,55,59,64,69],{"title":21,"path":22,"stem":23,"icon":24},"Membership Invitations","\u002Fdocs\u002Fkey-features\u002Fmembership-invitations","docs\u002F3.key-features\u002F1.membership-invitations","i-hugeicons-cube",{"title":26,"path":27,"stem":28,"icon":29},"Command Palette","\u002Fdocs\u002Fkey-features\u002Fcommand-palette","docs\u002F3.key-features\u002F10.command-palette","i-hugeicons-search-01",{"title":31,"path":32,"stem":33,"icon":34},"Library Import and Export","\u002Fdocs\u002Fkey-features\u002Flibrary-import-export","docs\u002F3.key-features\u002F11.library-import-export","i-lucide-package-open",{"title":36,"path":37,"stem":38,"icon":39},"LLM Settings Configuration","\u002Fdocs\u002Fkey-features\u002Fllm-settings-configuration","docs\u002F3.key-features\u002F2.llm-settings-configuration","i-hugeicons-ai-magic",{"title":41,"path":42,"stem":43,"icon":44},"Agent Code Review Findings","\u002Fdocs\u002Fkey-features\u002Fagent-code-review-findings","docs\u002F3.key-features\u002F3.agent-code-review-findings","i-lucide-list-checks",{"title":46,"path":47,"stem":48,"icon":49},"Credentials & API Tokens","\u002Fdocs\u002Fkey-features\u002Fcredentials","docs\u002F3.key-features\u002F4.credentials","i-hugeicons-key-01",{"title":51,"path":52,"stem":53,"icon":54},"Manual Document Libraries","\u002Fdocs\u002Fkey-features\u002Fmanual-document-libraries","docs\u002F3.key-features\u002F5.manual-document-libraries","i-lucide-library-big",{"title":56,"path":57,"stem":58,"icon":29},"Snippet Search","\u002Fdocs\u002Fkey-features\u002Fsnippet-search","docs\u002F3.key-features\u002F6.snippet-search",{"title":60,"path":61,"stem":62,"icon":63},"Shared Prompt Fragment","\u002Fdocs\u002Fkey-features\u002Fshared-prompt-fragment","docs\u002F3.key-features\u002F7.shared-prompt-fragment","i-lucide-notebook-pen",{"title":65,"path":66,"stem":67,"icon":68},"Code Review Sources & Telemetry","\u002Fdocs\u002Fkey-features\u002Fcode-review-sources","docs\u002F3.key-features\u002F8.code-review-sources","i-hugeicons-book-01",{"title":70,"path":71,"stem":72,"icon":73},"Excluding Documents from Code Reviews","\u002Fdocs\u002Fkey-features\u002Fcode-review-document-exclusion","docs\u002F3.key-features\u002F9.code-review-document-exclusion","i-lucide-eye-off",{"title":75,"path":76,"stem":77,"children":78,"page":6},"Runtime Configuration","\u002Fdocs\u002Fconfiguration","docs\u002F5.configuration",[79,84,89,93],{"title":80,"path":81,"stem":82,"icon":83},"Google Cloud Configuration","\u002Fdocs\u002Fconfiguration\u002Fgcp-runtime","docs\u002F5.configuration\u002F1.gcp-runtime","i-hugeicons-google",{"title":85,"path":86,"stem":87,"icon":88},"GitHub Configuration","\u002Fdocs\u002Fconfiguration\u002Fgithub-configuration","docs\u002F5.configuration\u002F2.github-configuration","i-hugeicons-github",{"title":90,"path":91,"stem":92},"Local OpenIddict Certificates","\u002Fdocs\u002Fconfiguration\u002Flocal-openiddict-certificates","docs\u002F5.configuration\u002F3.local-openiddict-certificates",{"title":94,"path":95,"stem":96,"icon":97},"System Admin Access","\u002Fdocs\u002Fconfiguration\u002Fsystem-admin-access","docs\u002F5.configuration\u002F4.system-admin-access","i-hugeicons-shield-01",{"title":99,"path":100,"stem":101,"children":102,"page":6},"Metrics","\u002Fdocs\u002Fmetrics","docs\u002F6.metrics",[103,108],{"title":104,"path":105,"stem":106,"icon":107},"Metrics Dashboard","\u002Fdocs\u002Fmetrics\u002Fmetrics-dashboard","docs\u002F6.metrics\u002F1.metrics-dashboard","i-hugeicons-analytics-01",{"title":109,"path":110,"stem":111,"icon":112},"Metrics Pipeline","\u002Fdocs\u002Fmetrics\u002Fmetrics-pipeline","docs\u002F6.metrics\u002F2.metrics-pipeline","i-hugeicons-database-02",{"title":114,"path":115,"stem":116,"children":117,"page":6},"Design and Architecture","\u002Fdocs\u002Farchitecture","docs\u002F9.architecture",[118,123,128,133,137,141,145,150,154],{"title":119,"path":120,"stem":121,"icon":122},"Caching","\u002Fdocs\u002Farchitecture\u002Fcaching","docs\u002F9.architecture\u002F1.caching","i-lucide-database",{"title":124,"path":125,"stem":126,"icon":127},"Messaging and Queueing","\u002Fdocs\u002Farchitecture\u002Fmessaging-and-queueing","docs\u002F9.architecture\u002F2.messaging-and-queueing","i-lucide-message-square",{"title":129,"path":130,"stem":131,"icon":132},"GitHub Comment Rendering","\u002Fdocs\u002Farchitecture\u002Fgithub-comment-rendering","docs\u002F9.architecture\u002F3.github-comment-rendering","i-lucide-github",{"title":134,"path":135,"stem":136},"GitHub Integration","\u002Fdocs\u002Farchitecture\u002Fgithub-integration","docs\u002F9.architecture\u002F4.github-integration",{"title":138,"path":139,"stem":140},"LLM Settings and Secret Storage","\u002Fdocs\u002Farchitecture\u002Fllm-settings-and-secret-storage","docs\u002F9.architecture\u002F5.llm-settings-and-secret-storage",{"title":142,"path":143,"stem":144},"Organization Activation and Routes","\u002Fdocs\u002Farchitecture\u002Forganization-activation-and-routes","docs\u002F9.architecture\u002F6.organization-activation-and-routes",{"title":146,"path":147,"stem":148,"icon":149},"MCP Uploaded-Diff Code Reviews","\u002Fdocs\u002Farchitecture\u002Fmcp-uploaded-diff-code-reviews","docs\u002F9.architecture\u002F7.mcp-uploaded-diff-code-reviews","i-lucide-file-search",{"title":151,"path":152,"stem":153},"Repository Content Ingest","\u002Fdocs\u002Farchitecture\u002Frepository-content-ingest","docs\u002F9.architecture\u002F8.repository-content-ingest",{"title":155,"path":156,"stem":157},"Telemetry Ingest","\u002Fdocs\u002Farchitecture\u002Ftelemetry-ingest","docs\u002F9.architecture\u002F9.telemetry-ingest",{"id":159,"title":119,"body":160,"description":1181,"extension":1182,"links":1183,"meta":1184,"navigation":1185,"path":120,"seo":1186,"stem":121,"__hash__":1187},"docs\u002Fdocs\u002F9.architecture\u002F1.caching.md",{"type":161,"value":162,"toc":1164},"minimark",[163,168,177,180,184,187,197,200,206,216,229,233,236,243,246,250,332,336,341,523,533,537,542,545,643,647,650,762,766,769,775,779,782,863,867,877,979,983,989,1096,1099,1103,1109,1115,1119,1160],[164,165,167],"h2",{"id":166},"summary","Summary",[169,170,171,172,176],"p",{},"Zeeq uses ",[173,174,175],"code",{},"HybridCache"," as the single application cache API. This gives us the speed of local memory with the consistency of a shared distributed cache while keeping feature code insulated from the details of the backing provider.",[169,178,179],{},"The default distributed provider is PostgreSQL because Zeeq already runs on Postgres and cache data is disposable. This is intentionally pragmatic: we get most of the value of a shared cache without introducing Redis or another service on day one, while keeping the design open for Redis later if the workload calls for it.",[164,181,183],{"id":182},"how-it-works","How it works",[169,185,186],{},"Conceptually, the cache has two layers:",[188,189,195],"pre",{"className":190,"code":192,"language":193,"meta":194},[191],"language-text","Application code\n  -> HybridCache\n      -> L1: local memory inside the current process\n      -> L2: distributed cache provider\n          -> PostgreSQL today\n          -> Redis later if needed\n","text","",[173,196,192],{"__ignoreMap":194},[169,198,199],{},"The L1 cache is fast because it lives inside the current process. The L2 cache is shared because it lives outside the process and can be used by the web server and worker processes at the same time.",[169,201,202,203,205],{},"When a value is missing, ",[173,204,175],{}," runs the factory, stores the result, and serves later callers from cache. It also helps avoid the classic cache stampede problem where many callers all try to recompute the same missing value at the same time.",[169,207,208,209,211,212,215],{},"The important rule is simple: feature code should depend on ",[173,210,175],{},", not on Postgres, Redis, or ",[173,213,214],{},"IDistributedCache"," directly. That keeps call sites stable even if the L2 provider changes.",[217,218,219,223,224,228],"warning",{},[220,221,222],"strong",{},"Important note on the L1 + L2 behavior",": there is no distributed invalidation on change in the L2 cache.  So if one node changes the value, it does not automatically propagate to the L1, in memory cache for the other instances.  Therefore, it is important that if the value is subject to drift, it ",[225,226,227],"em",{},"only"," uses the L2 cache",[164,230,232],{"id":231},"why-postgres-first","Why Postgres first?",[169,234,235],{},"Postgres is already part of the Zeeq runtime, so using it as the first distributed cache keeps the system easier to run locally and easier to operate in the first production shape. There is no extra service to install, wire into Aspire, monitor, or provision.",[169,237,238,239,242],{},"The cache table is created by ",[173,240,241],{},"Microsoft.Extensions.Caching.Postgres",", not by EF Core migrations. This is different from application data because cache data is not part of the durable domain model.",[169,244,245],{},"Zeeq uses an unlogged Postgres table for cache entries by default. That means Postgres skips write-ahead logging for cache writes, which reduces write overhead and WAL volume for data that can be regenerated. The trade-off is acceptable here: cache entries may be lost after an unclean database shutdown, but the application can rebuild them.",[164,247,249],{"id":248},"key-pieces","Key pieces",[251,252,253,266],"table",{},[254,255,256],"thead",{},[257,258,259,263],"tr",{},[260,261,262],"th",{},"Piece",[260,264,265],{},"What it does",[267,268,269,279,291,300,310,322],"tbody",{},[257,270,271,276],{},[272,273,274],"td",{},[173,275,175],{},[272,277,278],{},"The API application code should use.",[257,280,281,285],{},[272,282,283],{},[173,284,214],{},[272,286,287,288,290],{},"The L2 abstraction used underneath ",[173,289,175],{},".",[257,292,293,297],{},[272,294,295],{},[173,296,241],{},[272,298,299],{},"The current distributed cache provider.",[257,301,302,307],{},[272,303,304],{},[173,305,306],{},"AppSettings:Cache",[272,308,309],{},"Provider, expiration, payload, and table configuration.",[257,311,312,317],{},[272,313,314],{},[173,315,316],{},"SetupCache.cs",[272,318,319,320,290],{},"Runtime setup for the L2 provider and ",[173,321,175],{},[257,323,324,329],{},[272,325,326],{},[173,327,328],{},"ConnectionStrings__zeeq-cache",[272,330,331],{},"Optional dedicated cache connection string.",[164,333,335],{"id":334},"configuration","Configuration",[169,337,338,339,290],{},"The main configuration section is ",[173,340,306],{},[251,342,343,356],{},[254,344,345],{},[257,346,347,350,353],{},[260,348,349],{},"Setting",[260,351,352],{},"Default",[260,354,355],{},"Notes",[267,357,358,373,388,403,418,433,448,463,478,493,508],{},[257,359,360,365,370],{},[272,361,362],{},[173,363,364],{},"Provider",[272,366,367],{},[173,368,369],{},"Postgres",[272,371,372],{},"The distributed cache provider.",[257,374,375,380,385],{},[272,376,377],{},[173,378,379],{},"SchemaName",[272,381,382],{},[173,383,384],{},"cache",[272,386,387],{},"The Postgres schema for the cache table.",[257,389,390,395,400],{},[272,391,392],{},[173,393,394],{},"TableName",[272,396,397],{},[173,398,399],{},"hybrid_cache",[272,401,402],{},"The Postgres cache table name.",[257,404,405,410,415],{},[272,406,407],{},[173,408,409],{},"CreateIfNotExists",[272,411,412],{},[173,413,414],{},"true",[272,416,417],{},"Lets the Postgres provider create the table at startup.",[257,419,420,425,430],{},[272,421,422],{},[173,423,424],{},"UseWAL",[272,426,427],{},[173,428,429],{},"false",[272,431,432],{},"Uses an unlogged table by default.",[257,434,435,440,445],{},[272,436,437],{},[173,438,439],{},"ExpiredItemsDeletionInterval",[272,441,442],{},[173,443,444],{},"00:30:00",[272,446,447],{},"How often expired Postgres cache rows are cleaned up.",[257,449,450,455,460],{},[272,451,452],{},[173,453,454],{},"DefaultSlidingExpiration",[272,456,457],{},[173,458,459],{},"00:20:00",[272,461,462],{},"L2 sliding expiration.",[257,464,465,470,475],{},[272,466,467],{},[173,468,469],{},"DefaultEntryExpiration",[272,471,472],{},[173,473,474],{},"00:10:00",[272,476,477],{},"HybridCache absolute expiration.",[257,479,480,485,490],{},[272,481,482],{},[173,483,484],{},"LocalCacheExpiration",[272,486,487],{},[173,488,489],{},"00:02:00",[272,491,492],{},"L1 in-memory expiration.",[257,494,495,500,505],{},[272,496,497],{},[173,498,499],{},"MaximumPayloadBytes",[272,501,502],{},[173,503,504],{},"1048576",[272,506,507],{},"Maximum serialized payload size.",[257,509,510,515,520],{},[272,511,512],{},[173,513,514],{},"MaximumKeyLength",[272,516,517],{},[173,518,519],{},"1024",[272,521,522],{},"Maximum cache key length.",[169,524,525,526,528,529,532],{},"The cache connection string comes from ",[173,527,328],{},". If that is not set, Zeeq falls back to ",[173,530,531],{},"ConnectionStrings__zeeq-db",". This lets development use the main database while leaving a clear path to a separate cache database later.",[164,534,536],{"id":535},"common-scenarios","Common scenarios",[538,539,541],"h3",{"id":540},"use-the-default-postgres-cache","Use the default Postgres cache",[169,543,544],{},"This is the normal setup and should be enough for local development and the first production deployment.",[188,546,550],{"className":547,"code":548,"language":549,"meta":194,"style":194},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"AppSettings\": {\n    \"Cache\": {\n      \u002F\u002F Registers Microsoft.Extensions.Caching.Postgres as the L2 provider.\n      \"Provider\": \"Postgres\"\n    }\n  }\n}\n","json",[173,551,552,561,580,596,603,625,631,637],{"__ignoreMap":194},[553,554,557],"span",{"class":555,"line":556},"line",1,[553,558,560],{"class":559},"sMK4o","{\n",[553,562,564,567,571,574,577],{"class":555,"line":563},2,[553,565,566],{"class":559},"  \"",[553,568,570],{"class":569},"spNyl","AppSettings",[553,572,573],{"class":559},"\"",[553,575,576],{"class":559},":",[553,578,579],{"class":559}," {\n",[553,581,583,586,590,592,594],{"class":555,"line":582},3,[553,584,585],{"class":559},"    \"",[553,587,589],{"class":588},"sBMFI","Cache",[553,591,573],{"class":559},[553,593,576],{"class":559},[553,595,579],{"class":559},[553,597,599],{"class":555,"line":598},4,[553,600,602],{"class":601},"sHwdD","      \u002F\u002F Registers Microsoft.Extensions.Caching.Postgres as the L2 provider.\n",[553,604,606,609,612,614,616,619,622],{"class":555,"line":605},5,[553,607,608],{"class":559},"      \"",[553,610,364],{"class":611},"sbssI",[553,613,573],{"class":559},[553,615,576],{"class":559},[553,617,618],{"class":559}," \"",[553,620,369],{"class":621},"sfazB",[553,623,624],{"class":559},"\"\n",[553,626,628],{"class":555,"line":627},6,[553,629,630],{"class":559},"    }\n",[553,632,634],{"class":555,"line":633},7,[553,635,636],{"class":559},"  }\n",[553,638,640],{"class":555,"line":639},8,[553,641,642],{"class":559},"}\n",[538,644,646],{"id":645},"keep-local-cache-entries-a-bit-longer","Keep local cache entries a bit longer",[169,648,649],{},"This can be useful in local development when you want fewer repeated reads while testing flows.",[188,651,653],{"className":547,"code":652,"language":549,"meta":194,"style":194},"{\n  \"AppSettings\": {\n    \"Cache\": {\n      \"Provider\": \"Postgres\",\n      \u002F\u002F L2 entries are shared across processes, so they can usually live longer.\n      \"DefaultEntryExpiration\": \"00:20:00\",\n      \u002F\u002F L1 entries are process-local, so keep them shorter to reduce stale reads.\n      \"LocalCacheExpiration\": \"00:05:00\"\n    }\n  }\n}\n",[173,654,655,659,671,683,702,707,725,730,747,752,757],{"__ignoreMap":194},[553,656,657],{"class":555,"line":556},[553,658,560],{"class":559},[553,660,661,663,665,667,669],{"class":555,"line":563},[553,662,566],{"class":559},[553,664,570],{"class":569},[553,666,573],{"class":559},[553,668,576],{"class":559},[553,670,579],{"class":559},[553,672,673,675,677,679,681],{"class":555,"line":582},[553,674,585],{"class":559},[553,676,589],{"class":588},[553,678,573],{"class":559},[553,680,576],{"class":559},[553,682,579],{"class":559},[553,684,685,687,689,691,693,695,697,699],{"class":555,"line":598},[553,686,608],{"class":559},[553,688,364],{"class":611},[553,690,573],{"class":559},[553,692,576],{"class":559},[553,694,618],{"class":559},[553,696,369],{"class":621},[553,698,573],{"class":559},[553,700,701],{"class":559},",\n",[553,703,704],{"class":555,"line":605},[553,705,706],{"class":601},"      \u002F\u002F L2 entries are shared across processes, so they can usually live longer.\n",[553,708,709,711,713,715,717,719,721,723],{"class":555,"line":627},[553,710,608],{"class":559},[553,712,469],{"class":611},[553,714,573],{"class":559},[553,716,576],{"class":559},[553,718,618],{"class":559},[553,720,459],{"class":621},[553,722,573],{"class":559},[553,724,701],{"class":559},[553,726,727],{"class":555,"line":633},[553,728,729],{"class":601},"      \u002F\u002F L1 entries are process-local, so keep them shorter to reduce stale reads.\n",[553,731,732,734,736,738,740,742,745],{"class":555,"line":639},[553,733,608],{"class":559},[553,735,484],{"class":611},[553,737,573],{"class":559},[553,739,576],{"class":559},[553,741,618],{"class":559},[553,743,744],{"class":621},"00:05:00",[553,746,624],{"class":559},[553,748,750],{"class":555,"line":749},9,[553,751,630],{"class":559},[553,753,755],{"class":555,"line":754},10,[553,756,636],{"class":559},[553,758,760],{"class":555,"line":759},11,[553,761,642],{"class":559},[538,763,765],{"id":764},"use-a-dedicated-cache-database","Use a dedicated cache database",[169,767,768],{},"Set the dedicated cache connection string when the cache should point at a different Postgres database or instance.",[188,770,773],{"className":771,"code":772,"language":193,"meta":194},[191],"ConnectionStrings__zeeq-cache=Host=localhost;Port=5432;Database=zeeq_cache;Username=zeeq;Password=...\n",[173,774,772],{"__ignoreMap":194},[538,776,778],{"id":777},"turn-on-wal-for-the-cache-table","Turn on WAL for the cache table",[169,780,781],{},"Most environments should not need this. Use it only when an environment has an operational reason to make cache rows WAL logged.",[188,783,785],{"className":547,"code":784,"language":549,"meta":194,"style":194},"{\n  \"AppSettings\": {\n    \"Cache\": {\n      \"Provider\": \"Postgres\",\n      \u002F\u002F This trades cache-write speed for WAL durability.\n      \"UseWAL\": true\n    }\n  }\n}\n",[173,786,787,791,803,815,833,838,851,855,859],{"__ignoreMap":194},[553,788,789],{"class":555,"line":556},[553,790,560],{"class":559},[553,792,793,795,797,799,801],{"class":555,"line":563},[553,794,566],{"class":559},[553,796,570],{"class":569},[553,798,573],{"class":559},[553,800,576],{"class":559},[553,802,579],{"class":559},[553,804,805,807,809,811,813],{"class":555,"line":582},[553,806,585],{"class":559},[553,808,589],{"class":588},[553,810,573],{"class":559},[553,812,576],{"class":559},[553,814,579],{"class":559},[553,816,817,819,821,823,825,827,829,831],{"class":555,"line":598},[553,818,608],{"class":559},[553,820,364],{"class":611},[553,822,573],{"class":559},[553,824,576],{"class":559},[553,826,618],{"class":559},[553,828,369],{"class":621},[553,830,573],{"class":559},[553,832,701],{"class":559},[553,834,835],{"class":555,"line":605},[553,836,837],{"class":601},"      \u002F\u002F This trades cache-write speed for WAL durability.\n",[553,839,840,842,844,846,848],{"class":555,"line":627},[553,841,608],{"class":559},[553,843,424],{"class":611},[553,845,573],{"class":559},[553,847,576],{"class":559},[553,849,850],{"class":559}," true\n",[553,852,853],{"class":555,"line":633},[553,854,630],{"class":559},[553,856,857],{"class":555,"line":639},[553,858,636],{"class":559},[553,860,861],{"class":555,"line":749},[553,862,642],{"class":559},[538,864,866],{"id":865},"future-redis-profile","Future Redis profile",[169,868,869,870,873,874,876],{},"The settings model already has a ",[173,871,872],{},"Redis"," provider value, but Redis is not wired as the active runtime package today. When Zeeq moves to Redis, application code should continue to use ",[173,875,175],{},"; only provider registration and configuration should change.",[188,878,880],{"className":547,"code":879,"language":549,"meta":194,"style":194},"{\n  \"AppSettings\": {\n    \"Cache\": {\n      \u002F\u002F This profile is reserved for the future Redis provider registration.\n      \"Provider\": \"Redis\",\n      \"DefaultEntryExpiration\": \"00:10:00\",\n      \"LocalCacheExpiration\": \"00:02:00\"\n    }\n  }\n}\n",[173,881,882,886,898,910,915,933,951,967,971,975],{"__ignoreMap":194},[553,883,884],{"class":555,"line":556},[553,885,560],{"class":559},[553,887,888,890,892,894,896],{"class":555,"line":563},[553,889,566],{"class":559},[553,891,570],{"class":569},[553,893,573],{"class":559},[553,895,576],{"class":559},[553,897,579],{"class":559},[553,899,900,902,904,906,908],{"class":555,"line":582},[553,901,585],{"class":559},[553,903,589],{"class":588},[553,905,573],{"class":559},[553,907,576],{"class":559},[553,909,579],{"class":559},[553,911,912],{"class":555,"line":598},[553,913,914],{"class":601},"      \u002F\u002F This profile is reserved for the future Redis provider registration.\n",[553,916,917,919,921,923,925,927,929,931],{"class":555,"line":605},[553,918,608],{"class":559},[553,920,364],{"class":611},[553,922,573],{"class":559},[553,924,576],{"class":559},[553,926,618],{"class":559},[553,928,872],{"class":621},[553,930,573],{"class":559},[553,932,701],{"class":559},[553,934,935,937,939,941,943,945,947,949],{"class":555,"line":627},[553,936,608],{"class":559},[553,938,469],{"class":611},[553,940,573],{"class":559},[553,942,576],{"class":559},[553,944,618],{"class":559},[553,946,474],{"class":621},[553,948,573],{"class":559},[553,950,701],{"class":559},[553,952,953,955,957,959,961,963,965],{"class":555,"line":633},[553,954,608],{"class":559},[553,956,484],{"class":611},[553,958,573],{"class":559},[553,960,576],{"class":559},[553,962,618],{"class":559},[553,964,489],{"class":621},[553,966,624],{"class":559},[553,968,969],{"class":555,"line":639},[553,970,630],{"class":559},[553,972,973],{"class":555,"line":749},[553,974,636],{"class":559},[553,976,977],{"class":555,"line":754},[553,978,642],{"class":559},[164,980,982],{"id":981},"usage","Usage",[169,984,985,986,988],{},"Inject ",[173,987,175],{}," and use stable keys.",[188,990,994],{"className":991,"code":992,"language":993,"meta":194,"style":194},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","public sealed class OrganizationReader(\n  HybridCache cache, \u002F\u002F 👈 Injected reference to HybridCache\n  OrganizationStore store\n)\n{\n    public async ValueTask\u003COrganizationSummary> GetSummaryAsync(\n        string organizationId,\n        CancellationToken cancellationToken\n    )\n    {\n        return await cache.GetOrCreateAsync(\n            \u002F\u002F Keys should name exactly what value is being cached.\n            $\"organization:summary:{organizationId}\",\n            \u002F\u002F This runs only on a miss; HybridCache coalesces concurrent misses for the same key.\n            async ct => await store.GetSummaryAsync(organizationId, ct),\n            cancellationToken: cancellationToken\n        );\n    }\n}\n","csharp",[173,995,996,1001,1006,1011,1016,1020,1025,1030,1035,1040,1045,1050,1056,1062,1068,1074,1080,1086,1091],{"__ignoreMap":194},[553,997,998],{"class":555,"line":556},[553,999,1000],{},"public sealed class OrganizationReader(\n",[553,1002,1003],{"class":555,"line":563},[553,1004,1005],{},"  HybridCache cache, \u002F\u002F 👈 Injected reference to HybridCache\n",[553,1007,1008],{"class":555,"line":582},[553,1009,1010],{},"  OrganizationStore store\n",[553,1012,1013],{"class":555,"line":598},[553,1014,1015],{},")\n",[553,1017,1018],{"class":555,"line":605},[553,1019,560],{},[553,1021,1022],{"class":555,"line":627},[553,1023,1024],{},"    public async ValueTask\u003COrganizationSummary> GetSummaryAsync(\n",[553,1026,1027],{"class":555,"line":633},[553,1028,1029],{},"        string organizationId,\n",[553,1031,1032],{"class":555,"line":639},[553,1033,1034],{},"        CancellationToken cancellationToken\n",[553,1036,1037],{"class":555,"line":749},[553,1038,1039],{},"    )\n",[553,1041,1042],{"class":555,"line":754},[553,1043,1044],{},"    {\n",[553,1046,1047],{"class":555,"line":759},[553,1048,1049],{},"        return await cache.GetOrCreateAsync(\n",[553,1051,1053],{"class":555,"line":1052},12,[553,1054,1055],{},"            \u002F\u002F Keys should name exactly what value is being cached.\n",[553,1057,1059],{"class":555,"line":1058},13,[553,1060,1061],{},"            $\"organization:summary:{organizationId}\",\n",[553,1063,1065],{"class":555,"line":1064},14,[553,1066,1067],{},"            \u002F\u002F This runs only on a miss; HybridCache coalesces concurrent misses for the same key.\n",[553,1069,1071],{"class":555,"line":1070},15,[553,1072,1073],{},"            async ct => await store.GetSummaryAsync(organizationId, ct),\n",[553,1075,1077],{"class":555,"line":1076},16,[553,1078,1079],{},"            cancellationToken: cancellationToken\n",[553,1081,1083],{"class":555,"line":1082},17,[553,1084,1085],{},"        );\n",[553,1087,1089],{"class":555,"line":1088},18,[553,1090,630],{},[553,1092,1094],{"class":555,"line":1093},19,[553,1095,642],{},[169,1097,1098],{},"Good cache keys are specific, stable, and scoped to the data being cached. Cache data that is expensive to compute or frequently reused. Do not cache data that must be immediately consistent unless the write path also invalidates or bypasses the cache.",[164,1100,1102],{"id":1101},"operational-notes","Operational notes",[169,1104,1105,1108],{},[173,1106,1107],{},"UseZeeqCacheAsync"," performs a small startup cache write so the Postgres cache table exists before the app starts serving traffic.",[169,1110,1111,1112,1114],{},"Cache data is disposable. It is safe to clear the cache table when troubleshooting stale values. Shorter ",[173,1113,484],{}," values reduce stale data inside one process, while longer L2 expiration values reduce repeated database work across processes.",[164,1116,1118],{"id":1117},"references","References",[1120,1121,1122,1132,1139,1146,1153],"ul",{},[1123,1124,1125],"li",{},[1126,1127,1131],"a",{"href":1128,"rel":1129},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Faspnet\u002Fcore\u002Fperformance\u002Fcaching\u002Fhybrid",[1130],"nofollow","HybridCache in ASP.NET Core",[1123,1133,1134],{},[1126,1135,1138],{"href":1136,"rel":1137},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Faspnet\u002Fcore\u002Fperformance\u002Fcaching\u002Foverview",[1130],"ASP.NET Core caching overview",[1123,1140,1141],{},[1126,1142,1145],{"href":1143,"rel":1144},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Faspnet\u002Fcore\u002Fperformance\u002Fcaching\u002Fdistributed",[1130],"Distributed caching in ASP.NET Core",[1123,1147,1148],{},[1126,1149,1152],{"href":1150,"rel":1151},"https:\u002F\u002Fwww.nuget.org\u002Fpackages\u002FMicrosoft.Extensions.Caching.Hybrid\u002F",[1130],"Microsoft.Extensions.Caching.Hybrid package",[1123,1154,1155],{},[1126,1156,1159],{"href":1157,"rel":1158},"https:\u002F\u002Fwww.nuget.org\u002Fpackages\u002FMicrosoft.Extensions.Caching.Postgres\u002F",[1130],"Microsoft.Extensions.Caching.Postgres package",[1161,1162,1163],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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":194,"searchDepth":563,"depth":563,"links":1165},[1166,1167,1168,1169,1170,1171,1178,1179,1180],{"id":166,"depth":563,"text":167},{"id":182,"depth":563,"text":183},{"id":231,"depth":563,"text":232},{"id":248,"depth":563,"text":249},{"id":334,"depth":563,"text":335},{"id":535,"depth":563,"text":536,"children":1172},[1173,1174,1175,1176,1177],{"id":540,"depth":582,"text":541},{"id":645,"depth":582,"text":646},{"id":764,"depth":582,"text":765},{"id":777,"depth":582,"text":778},{"id":865,"depth":582,"text":866},{"id":981,"depth":563,"text":982},{"id":1101,"depth":563,"text":1102},{"id":1117,"depth":563,"text":1118},"Overview of caching systems in the architecture.","md",null,{},{"icon":122},{"title":119,"description":1181},"8JuqITSYOaG0iriGUfWklSDDbbAAnrfE7rXXE7zQTvM",[1189,1191],{"title":109,"path":110,"stem":111,"description":1190,"icon":112,"children":-1},"How application metrics are captured, batched, stored, and served for the dashboard.",{"title":124,"path":125,"stem":126,"description":1192,"icon":127,"children":-1},"Overview of messaging and queueing systems in the architecture.",1784297365800]