{"Entry":{"collection":"plan","key":"memoize","name":"Memoize","aliases":["Memoize","T_Memoize"],"metadata":{"aliases":["Memoize","T_Memoize"],"category":"Materialization","content_hash":"f89fcc4f6ea3f2e1708296ae392fd1c44fe7caa8a6c932d46ae40983f766b96b","derived_content_hash":"96bdd8048a3176830180a5061cee9be67826f1f1c7a1b68846b62fbf351d08c5","imported_at":"2026-09-30T00:40:44.158534+08:00","name":"Memoize","name_zh":"Memoize","slug":"memoize","summary":"Caches results from a parameterized child and reuses them when the same parameter values recur."}},"Definition":{"Collection":"plan","Key":"memoize","SourceDatabase":"center","Version":"18","SourceTable":"plan_node","SourceKey":"memoize","SourceRevision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","Facts":{"comparison_data":{"initializer":"ExecInitMemoize","memory_mechanism":"eviction","node_tag":"T_Memoize","parallel_callbacks":["ExecMemoizeEstimate","ExecMemoizeInitializeDSM","ExecMemoizeInitializeWorker","ExecMemoizeRetrieveInstrumentation"],"partial_modes":[],"strategies":[],"text_names":["Memoize"]},"comparison_hash":"ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b","description":["Caches results from a parameterized child and reuses them when the same parameter values recur."],"description_zh":"缓存参数化子计划的结果，在相同参数值再次出现时复用。","description_zh_html":"\u003cp\u003e缓存参数化子计划的结果，在相同参数值再次出现时复用。\u003c/p\u003e\u003cp\u003e核心节点标识：\u003ccode\u003eT_Memoize\u003c/code\u003e；EXPLAIN 名称：\u003ccode\u003eMemoize\u003c/code\u003e。\u003c/p\u003e\u003cp\u003e本版本源码中的并行执行或观测回调：\u003ccode\u003eExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation\u003c/code\u003e。回调的存在不表示该节点必然支持共享并行扫描；在并行工作进程中执行也不等于具有并行感知能力。\u003c/p\u003e\u003ch3\u003e同版本中文手册节选\u003c/h3\u003e\u003cp\u003e\n\u003cspan class=\"productname\"\u003ePostgreSQL\u003c/span\u003e 会为收到的每个查询制定一个\u003cem class=\"firstterm\"\u003e查询计划\u003c/em\u003e。选择与查询结构和数据特性相匹配的正确计划，对获得良好的性能至关重要，因此系统内置了一个复杂的\u003cem class=\"firstterm\"\u003e规划器\u003c/em\u003e来尽量选出好的计划。可以使用 \u003ca class=\"link\" href=\"/zh/docs/18/sql-explain.html\"\u003e\u003ccode class=\"command\"\u003eEXPLAIN\u003c/code\u003e\u003c/a\u003e 命令查看规划器为任意查询生成的查询计划。读懂计划是一门需要经验积累的技能，本节将尝试介绍其中的基础知识。\n   \u003c/p\u003e\u003cp\u003e\n    本节中的示例取自执行过 \u003ccode class=\"command\"\u003eVACUUM ANALYZE\u003c/code\u003e 的回归测试数据库，使用的是 v18 开发版本源码。如果自行尝试这些示例，通常应当能得到相近的结果，但估计代价和行计数可能略有差异，因为 \u003ccode class=\"command\"\u003eANALYZE\u003c/code\u003e 生成的统计信息来自随机采样而非精确计数，而且代价本身在某种程度上也依赖于平台。\n   \u003c/p\u003e\u003cp\u003e\n    这些示例使用 \u003ccode class=\"command\"\u003eEXPLAIN\u003c/code\u003e 默认的\u003cspan class=\"quote\"\u003e“\u003cspan class=\"quote\"\u003etext\u003c/span\u003e”\u003c/span\u003e输出格式，它紧凑且便于人工阅读。如果希望将 \u003ccode class=\"command\"\u003eEXPLAIN\u003c/code\u003e 的输出交给程序做进一步分析，则应改用机器可读的输出格式（XML、JSON 或 YAML）。\n   \u003c/p\u003e\u003cp\u003e\u003ca href=\"/zh/docs/18/using-explain.html\"\u003e查看完整中文手册与上下文\u003c/a\u003e。\u003c/p\u003e\u003cp\u003e本页开头的中文说明为 PG.CENTER 对编辑说明的翻译；手册节选保留对应版本中文文档原文。源码信息及手册示例不代表重新进行过运行测试。\u003c/p\u003e","evidence_kind":"source and documentation","explain_names":["Memoize"],"explain_prefixes":["Parallel","Async"],"facts":[{"label":"Core node tag","value":"T_Memoize"},{"label":"Structured EXPLAIN Node Type","value":"Memoize"},{"label":"Inputs","value":"One parameterized child plan"},{"label":"Output","value":"Cached or newly produced child tuples"},{"label":"Executor initializer","value":"ExecInitMemoize"},{"label":"Memory mechanism","value":"eviction"}],"facts_zh":[{"label":"核心节点标签","value":"T_Memoize"},{"label":"结构化 EXPLAIN 节点类型","value":"Memoize"},{"label":"输入","value":"一个参数化子计划"},{"label":"输出","value":"已缓存或新产生的子计划元组"},{"label":"执行器初始化函数","value":"ExecInitMemoize"},{"label":"内存机制","value":"eviction"}],"memory":{"description":"The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.","evidence":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeMemoize.c","path":"src/backend/executor/nodeMemoize.c","sha256":"1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"}],"mechanism":"eviction","source_notes":["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.","It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.","cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.","If we've gone over our memory budget, then we'll free up some space in the cache."]},"node_tag":"T_Memoize","parallel_callbacks":["ExecMemoizeEstimate","ExecMemoizeInitializeDSM","ExecMemoizeInitializeWorker","ExecMemoizeRetrieveInstrumentation"],"partial_modes":[],"related":[{"label":"EXPLAIN","url":"/wiki/sql/explain/?v=18"},{"label":"Using EXPLAIN","url":"/docs/18/using-explain.html"},{"label":"Parallel plans","url":"/docs/18/parallel-plans.html"},{"label":"enable_memoize","url":"/wiki/guc/enable_memoize/?v=18"},{"label":"hash_mem_multiplier","url":"/wiki/guc/hash_mem_multiplier/?v=18"}],"release":{"channel":"stable","label":"18.6","major":"18","ref":"PostgreSQL 18.6 source archive","revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","source_snapshot_utc":"","source_url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},"runtime_verified":false,"sections":[{"paragraphs":["Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.","Text names recorded by this source: Memoize.","Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node."],"title":"EXPLAIN names and attributes"},{"paragraphs":["The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.","The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.","It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.","cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.","If we've gone over our memory budget, then we'll free up some space in the cache."],"title":"Memory and temporary storage"},{"paragraphs":["The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.","Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."],"title":"Parallel execution and instrumentation"},{"paragraphs":["nodeMemoize.c Routines to handle caching of results from parameterized nodes","Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.","The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.","Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.","It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again."],"title":"Executor implementation notes"},{"code":"case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;","title":"EXPLAIN identity in core source"}],"sections_zh":[{"paragraphs":["结构化格式使用上述 Node Type。文本格式名称还可能包含操作、策略、连接类型、扫描方向或聚合阶段属性。","此源码记录的文本名称：Memoize.","并行感知与并行安全是不同的计划属性。在并行工作进程内运行的节点不一定是并行感知节点。"],"title":"EXPLAIN 名称与属性"},{"paragraphs":["Memoize 缓存淘汰最近最少使用的条目，不将缓存元组写入磁盘。如果结果无法放入缓存，该次扫描可以进入旁路模式。","缓存采用哈希表。缓存用满时不会将元组写入磁盘，而是淘汰最近最少使用的缓存条目。通过把新条目和每次查找的条目移到双向链表末尾来记录使用次序，使较久未用的条目逐渐移到 LRU 列表头部。","为一组参数填充缓存时，可能无法释放足够的内存来存储更多元组。此时其他缓存条目已经全部被淘汰。如果再缓存一个元组会超出预算，必须释放正在填充的条目，并将状态机切换为 MEMO_CACHE_BYPASS_MODE。这意味着本次扫描不再尝试缓存后续元组，因为内存不足。下次重新扫描时状态机会重置；如果下一组参数的元组缓存需要较少内存，就可能重新开始缓存有用的条目。","cache_lookup 按扫描的当前参数查找是否已缓存元组。找到现有条目时，将其移到 LRU 列表末尾，将 *found 设为 true 并返回。找不到时，新建条目并加入列表末尾，同时更新缓存内存记账；如果超出预算则移除较旧条目。成功释放足够内存时返回新条目，否则返回 NULL。","如果超出内存预算，就释放部分缓存空间。"],"title":"内存与临时存储"},{"paragraphs":["以下源码回调可以协调执行或收集工作进程的测量数据。回调存在不代表该节点普遍支持共享并行扫描或共享状态。","此构建的回调：ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."],"title":"并行执行与运行信息采集"},{"paragraphs":["nodeMemoize.c：缓存参数化节点结果的例程。","Memoize 节点位于计划树中的参数化节点之上，用来缓存其结果。使用已经出现过的参数值再次扫描时，可以直接从缓存读取元组，无需重新扫描内部节点。规划器认为重复使用已有参数值的概率足以抵偿额外节点开销时，可能选择这种节点。","缓存采用哈希表。缓存用满时不会将元组写入磁盘，而是淘汰最近最少使用的缓存条目。通过把新条目和每次查找的条目移到双向链表末尾来记录使用次序，使较久未用的条目逐渐移到 LRU 列表头部。","调用者有时不会完成全部扫描。例如，半连接找到一个匹配元组后就跳到下一个外侧元组，不再继续此次内侧扫描。因此必须跟踪缓存条目是否完整；通常扫描读不到更多元组时才可确认完整。不过某些情况下不必耗尽扫描便能标记完整。例如唯一连接已知每个外侧元组最多匹配一行。为支持这种情况，缓存允许设置 singlerow 选项，在从子节点读取第一个元组后即标记该缓存条目完整。","为一组参数填充缓存时，可能无法释放足够的内存来存储更多元组。此时其他缓存条目已经全部被淘汰。如果再缓存一个元组会超出预算，必须释放正在填充的条目，并将状态机切换为 MEMO_CACHE_BYPASS_MODE。这意味着本次扫描不再尝试缓存后续元组，因为内存不足。下次重新扫描时状态机会重置；如果下一组参数的元组缓存需要较少内存，就可能重新开始缓存有用的条目。"],"title":"执行器实现说明"},{"code":"case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;","title":"核心源码中的 EXPLAIN 标识"}],"source_inventory":{"executor":"src/backend/executor/execProcnode.c","explain":"src/backend/commands/explain.c","implementation":"src/backend/executor/nodeMemoize.c"},"sources":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/commands/explain.c:1519","line":1519,"path":"src/backend/commands/explain.c","sha256":"34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/execProcnode.c:330","line":330,"path":"src/backend/executor/execProcnode.c","sha256":"f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeMemoize.c","path":"src/backend/executor/nodeMemoize.c","sha256":"1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/include/nodes/plannodes.h","path":"src/include/nodes/plannodes.h","sha256":"52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"}],"strategies":[],"tables":[{"columns":[{"key":"label","label":"Text-format label"},{"key":"identity","label":"Structured node identity"}],"key":"explain-labels","rows":[{"identity":"Memoize","label":"Memoize"}],"title":"EXPLAIN labels in this source build"}],"tables_zh":[{"columns":[{"key":"label","label":"文本格式标签"},{"key":"identity","label":"结构化节点标识"}],"key":"explain-labels","rows":[{"identity":"Memoize","label":"Memoize"}],"title":"本构建中的 EXPLAIN 标签"}],"zh_from":{"authored_translation":"authored-translation-of-reviewed-guidance","body_sha256":"db82941f3f87442a78ada39f4d381be4536c3a1f4118325c16ec75456ce1f62f","kind":"same-version-chinese-manual-with-authored-translation-of-reviewed-guidance","manual":{"anchor":"","build_id":107,"extraction":"concept-introduction","fragment_sha256":"15fbdf84b8f499253ade4f4a5841de138619a9933b93cc241d431dbe922fa40c","locale":"zh-Hans","page_key":"using-explain.html","source_hash":"d2f749744b3ac5aa2eb0154f295e8163903cd20fcc130c74c6172cdf2ae3a9df","source_revision":"unknown:rendered-translation-baseline","translated_from_revision":null,"translation_revision":"git:b1b500221dbe5437cfca004d6b04d3f6e422765d;html-sha256:08e987a66af27b98444a8057c256314eafa13611a3fb9e888214fc436824244b","url":"/zh/docs/18/using-explain.html","version":"18"},"runtime_rerun":false,"source_facts_revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}},"ManualEvidence":{"release":{"channel":"stable","label":"18.6","major":"18","ref":"PostgreSQL 18.6 source archive","revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","source_snapshot_utc":"","source_url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},"sources":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/commands/explain.c:1519","line":1519,"path":"src/backend/commands/explain.c","sha256":"34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/execProcnode.c:330","line":330,"path":"src/backend/executor/execProcnode.c","sha256":"f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeMemoize.c","path":"src/backend/executor/nodeMemoize.c","sha256":"1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/include/nodes/plannodes.h","path":"src/include/nodes/plannodes.h","sha256":"52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"}]},"MeasuredEvidence":{"runtime_verified":false}},"Text":{"Collection":"plan","Key":"memoize","SourceDatabase":"center","Version":"18","Locale":"en","Title":"Memoize","Summary":"Caches results from a parameterized child and reuses them when the same parameter values recur.","BodyHTML":"\u003cp\u003eCaches results from a parameterized child and reuses them when the same parameter values recur.\u003c/p\u003e","SourceRevision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","ContentHash":"2c972bb734ba865d45d817ff8fac1c36b943538dadc1d0d2ef51ce296e1213ca","Payload":{"description":["Caches results from a parameterized child and reuses them when the same parameter values recur."],"related":[{"label":"EXPLAIN","url":"/wiki/sql/explain/?v=18"},{"label":"Using EXPLAIN","url":"/docs/18/using-explain.html"},{"label":"Parallel plans","url":"/docs/18/parallel-plans.html"},{"label":"enable_memoize","url":"/wiki/guc/enable_memoize/?v=18"},{"label":"hash_mem_multiplier","url":"/wiki/guc/hash_mem_multiplier/?v=18"}],"sections":[{"paragraphs":["Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.","Text names recorded by this source: Memoize.","Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node."],"title":"EXPLAIN names and attributes"},{"paragraphs":["The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.","The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.","It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.","cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.","If we've gone over our memory budget, then we'll free up some space in the cache."],"title":"Memory and temporary storage"},{"paragraphs":["The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.","Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."],"title":"Parallel execution and instrumentation"},{"paragraphs":["nodeMemoize.c Routines to handle caching of results from parameterized nodes","Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.","The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.","Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.","It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again."],"title":"Executor implementation notes"},{"code":"case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;","title":"EXPLAIN identity in core source"}],"tables":[{"columns":[{"key":"label","label":"Text-format label"},{"key":"identity","label":"Structured node identity"}],"key":"explain-labels","rows":[{"identity":"Memoize","label":"Memoize"}],"title":"EXPLAIN labels in this source build"}]}},"RequestedLocale":"en","Fallback":false,"Versions":["14","15","16","17","18","19","20"],"Locales":["en","zh-Hans"],"Signatures":null,"Spellings":null,"SQLState":null,"Evidence":null}
