FAIRYFAR-INTERNAL
 
  FAIRYFAR-INTERNAL  |  SITEMAP  |  ABOUT-ME  |  HOME  
PostgreSQL并行查询中的动态共享内存机制

本文适用于PostgreSQL 14。

一、概述

PosgreSQL 9.6开始支持并行查询(Parallel Query)[1],这个版本的对并行的支持还非常有限,之后的发行版本不断进行了完善。不同于多线程模式,PostgreSQL采用多进程模式,并行查询将把一条查询任务拆分给多个 worker 分布执行。因此,需要解决不同进程之间数据共享问题。为此,PostgreSQL引入了动态共享内存(DSM,Dynamic Shared Memory)机制。

二、动态共享内存

共享内存

守护进程 PostMaster 初始化阶段初始化共享内存信息,代码主要集中在 CreateSharedMemoryAndSemaphores 函数,代码片段:

snippet.c
void
CreateSharedMemoryAndSemaphores(void)
{
	PGShmemHeader *shim = NULL;
	if (!IsUnderPostmaster)
	{
    Size size;
    size = 100000;
    size = add_size(size, PGSemaphoreShmemSize(numSemas));
    size = add_size(size, SpinlockSemaSize());
    size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE, sizeof(ShmemIndexEnt)));
    size = add_size(size, dsm_estimate_size());
	  ……
		/*
		 * Create the shmem segment
		 */
		seghdr = PGSharedMemoryCreate(size, &shim);
	}
  ……
	/* Initialize dynamic shared memory facilities. */
	if (!IsUnderPostmaster)
		dsm_postmaster_startup(shim);
}
  • PGSharedMemoryCreate:创建一个初始大小的共享内存,并初始化header(PGShmemHeader),size是预估的初始共享内存大小。
  • dsm_postmaster_startup:启动DSM。将初始化DSM段控制器 dsm_control。后续使用会创建多个DSM段,DSM段将由 dsm_control 进行维护。

DSM段

所有DSM段由 dsm_control 维护,数据结构如下:

snippet.c
/* Shared-memory state for a dynamic shared memory segment. */
typedef struct dsm_control_item
{
	dsm_handle handle;
	uint32 refcnt;				  /* 2+ = active, 1 = moribund, 0 = gone */
	void *impl_private_pm_handle; /* only needed on Windows */
	bool pinned;
} dsm_control_item;
 
/* Layout of the dynamic shared memory control segment. */
typedef struct dsm_control_header
{
	uint32 magic;
	uint32 nitems;
	uint32 maxitems;
	dsm_control_item item[FLEXIBLE_ARRAY_MEMBER];
} dsm_control_header;
 
static dsm_control_header *dsm_control;
  • dsm_control_header::item:是变长数组,元素为DSM段信息。

DSM段的TOC

一个DSM段可能会被分有很多内存片(chunk),这些 chunk 使用“目录(TOC,Table of Contents)”进行检索和管理。

snippet.c
typedef struct shm_toc_entry
{
	uint64 key;	 /* Arbitrary identifier */
	Size offset; /* Offset, in bytes, from TOC start */
} shm_toc_entry;
 
struct shm_toc
{
	uint64 toc_magic;		  /* Magic number identifying this TOC */
	slock_t toc_mutex;		  /* Spinlock for mutual exclusion */
	Size toc_total_bytes;	  /* Bytes managed by this TOC */
	Size toc_allocated_bytes; /* Bytes allocated of those managed */
	uint32 toc_nentry;		  /* Number of entries in TOC */
	shm_toc_entry toc_entry[FLEXIBLE_ARRAY_MEMBER];
};

并行执行器的DSM段

关注 InitializeParallelDSM 这个函数,在算子需要并行执行时,首先会为并行上下文初始化一个DSM段。

InitializeParallelDSM 创建和使用DSM过程:

  1. shm_toc_estimate_chunk:使用这个函数评估将要使用的对象内存 chunk 大小,每个将要分配内存 chunk 都需要调用 shm_toc_estimate_chunk 进行评估,累计在 shm_toc_estimator::space_for_chunks 成员变量上。
snippet.c
   /*
    * Tools for estimating how large a chunk of shared memory will be needed
    * to store a TOC and its dependent objects.  Note: we don't really support
    * large numbers of keys, but it's convenient to declare number_of_keys
    * as a Size anyway.
    */
   typedef struct
   {
   	Size		space_for_chunks;
   	Size		number_of_keys;
   } shm_toc_estimator;
  1. shm_toc_estimate_keys:为内存 chunk 设置 key,可以通过 key 在DSM段的TOC中检索内存 chunk。key 的总个数累计在 shm_toc_estimator::number_of_keys 成员变量上。
  2. shm_toc_estimate:根据以上累计的大小,评估DSM段大小,设为 segsize。
  3. dsm_create:分配 segsize 大小的共享内存段并且 mmap 。
  4. shm_toc_allocate:分配一个空闲地址用于保存 chunk 内容,相对于 malloc ,不同的是,该函数通过计算内存偏移地址方式,从步骤4的DSM段里查找一个空闲内存位置。返回地址值。之后,我们就可以对这段内存进行写操作了。
  5. shm_toc_insert:步骤5中分配了空闲地址,但是还没有登记,本函数在段的TOC中登记,即,在目录中追加一条记录。

DSM段内存布局

并行查询的DSM段TOC保存在 ParallelContext 中:

snippet.c
typedef struct ParallelContext
{
	shm_toc_estimator estimator;
	dsm_segment *seg;
	void	   *private_memory;
	shm_toc    *toc;
  ……
} ParallelContext;

ParallelContext::toc 实际就是 dsm_create 函数内部 mmap 的共享内存首地址,因此,shm_toc内存布局实际就是DSM段的内存布局。如下图所示,chunk 内存使用在DSM段中从后向前分配,而TOC项是从前向后分配内存。

  • shm_toc_entry::offset 指示 chunk 在段中的偏移地址,相对于 ParallelContext::toc 的偏移。
  • shm_toc::toc_allocated_bytes 记录所有 chunk 大小之和。
  • 当 chunk 空间与 toc 空间即将交叠时,表明当前的DSM段内存使用完,禁止继续分配。
  • 可以参考 shm_toc_allocate 和 shm_toc_insert 两个函数,分析DSM段内存使用过程。

shm_toc

三、参考



打赏作者以资鼓励: