pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Jack Ng <Jack.Ng@huawei.com>
To: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Cc: Dmitry Dolgov <9erthalion6@gmail.com>
Cc: pgsql-hackers@postgresql.org <pgsql-hackers@postgresql.org>
Cc: Robert Haas <robertmhaas@gmail.com>
Cc: Ni Ku <jakkuniku@gmail.com>
Subject: RE: Changing shared_buffers without restart
Date: Tue, 13 May 2025 05:03:03 +0000
Message-ID: <73f05b290b584e0581c4a96a33daa77b@huawei.com> (raw)
In-Reply-To: <CAExHW5sNCQdZsUH8qPZ5c1qJHWzhP5K3LJkC+MOE+jX6vTKMyA@mail.gmail.com>
References: <CAExHW5soDB=08jeyqn-W-=+Y86v-a5f_NJRjAdV9_yFrKpJcCw@mail.gmail.com>
	<eqs6v4rsboazl67xz3wxc6xjkgrpfybitpl45y3lmb2br67wbj@o7czebb3rlgd>
	<CAExHW5sTB1Ow0a1JtVy4PGCPckM7P1CSngJ4+LX3w+yz7rwQfQ@mail.gmail.com>
	<ywyt5gecsfjzxlvxbvingd2dps2dedpwq5l4xhfmbcci6xta2e@h7ygoxvep725>
	<CAExHW5s1b-_9GFGGOxSVimrt8oe2Y0a7vRhENrd2BSo+0Tkp-A@mail.gmail.com>
	<gzb2oqqabzqidlnumrbau2k2fscb5jo6nlpdjnf6pu5y4bk5mo@xoujqgtiqvgr>
	<CAExHW5uGozdiDm_BefS7jpQzaisCs4_nLTMF0+S_Pds49pRG_w@mail.gmail.com>
	<CAPuPUJwDa_WEVmkknyYHYub5qvBuwKyLLOOiUpyiq6Wd=9FQ_g@mail.gmail.com>
	<a2b6laawu4iya4zu737lhgdbvi5bcspxkxqinzogw6o7e2yhke@ay4qtrdlyzhq>
	<fbdc6674ae54420d96368079bf76dd74@huawei.com>
	<urny6igvtdqmbumsjmnpvw2vkynbjsxcyunov57gzp4bsikrh4@3pmdg245fkbe>
	<dcd396b2e89c4b83a34adaaec5838476@huawei.com>
	<CAExHW5sNCQdZsUH8qPZ5c1qJHWzhP5K3LJkC+MOE+jX6vTKMyA@mail.gmail.com>

Hi Ashutosh,

> > * During resize, simply calculate the new size and call ftruncate on each
> > segment to adjust memory accordingly, no need to mmap/munmap or modify any
> > memory mapping.
> >
> >
> That's same as my understanding.
Great, thanks for confirming!

> I thought I had shared a test program upthread, but I don't find it now. Attached here. Can you please share your test program?
Sure, mine is attached here (it’s based on another test program you shared before :-)

Regards,

Jack Ng

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#include <string.h>
#include <errno.h>


#define HUGE_PG_SZ ((size_t)(2048*1024))
unsigned long long MAX_SZ = (100 * ((size_t)(1024 * 1024) * (size_t)(1024 * 1024))); // 100 TB

int main(int argc, char *argv[]) {
  int fd_1 = -1;
  int fd_2 = -1;
  size_t old_size = atoi(argv[1]) * HUGE_PG_SZ;
  size_t new_size = atoi(argv[2]) * HUGE_PG_SZ;

  // Probe for a valid address that can grow up to MAX_SZ
  void *probe = mmap(NULL, MAX_SZ,
                     PROT_READ | PROT_WRITE,
                     MAP_ANONYMOUS | MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, -1, 0);
  if (probe == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  if (munmap(probe, MAX_SZ) == -1) {
    perror("munmap");
    exit(1);
  }

  /*

  |----- Memory mapping (MAP_NORESERVE) #1 -----|----- Memory mapping (MAP_NORESERVE) #2 -----|
  |-- In-use region 1 --|-- Reserved region 1 --|-- In-use region 2 --|-- Reserved region 2 --|

  */

  // Create anon mapping #1 that's backed by an anon file fd_1
  fd_1 = memfd_create("test_hugepage_map1", MFD_HUGETLB | MFD_HUGE_2MB | MFD_CLOEXEC);
  if (fd_1 == -1) {
    perror("memfd_create");
    exit(1);
  }

  void *map1_addr = mmap(probe, MAX_SZ/2,
                         PROT_READ | PROT_WRITE,
                         MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, fd_1, 0);
  if (map1_addr == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  // Create in-use region 1
  if (ftruncate(fd_1, old_size) == -1) {
    perror("ftruncate");
    exit(1);
  }

  // The difference between ftruncate and fallocate is that ftruncate does not actually
  // allocate any memory yet (rather it's allocated on "first touch") but fallocate
  // does and also initializes the memory with zeros.
  /*if (fallocate(fd_1, 0, 0, old_size) == -1) {
    perror("fallocate");
    exit(1);
  }*/

  // Optionally, we can protect the reserved region just to be safe
  if (mprotect(map1_addr + old_size, MAX_SZ/2 - old_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }


  // Create anon mapping #2 that's backed by an anon file fd_2
  fd_2 = memfd_create("test_hugepage_map2", MFD_HUGETLB | MFD_HUGE_2MB | MFD_CLOEXEC);
  if (fd_2 == -1) {
    perror("memfd_create");
    exit(1);
  }

  void *map2_addr = mmap(probe + MAX_SZ/2, MAX_SZ/2,
                         PROT_READ | PROT_WRITE,
                         MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, fd_2, 0);
  if (map2_addr == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  // Create in-use region 2
  if (ftruncate(fd_2, old_size) == -1) {
    perror("ftruncate");
    exit(1);
  }
  /*if (fallocate(fd_2, 0, 0, old_size) == -1) {
    perror("fallocate");
    exit(1);
  }*/

  if (mprotect(map2_addr + old_size, MAX_SZ/2 - old_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }

  unsigned char *in_use1 = (unsigned char*)map1_addr;
  unsigned char *in_use2 = (unsigned char*)map2_addr;

  printf("Press Enter to fill in-use segment 1 memory...\n");
  getchar();
  memset(map1_addr, 0xee, old_size);

  printf("Press Enter to fill in-use segment 2 memory...\n");
  getchar();
  memset(map2_addr, 0xdd, old_size);

  for (int i=0; i < old_size; i++) {
    if (in_use1[i] != 0xee) {
      printf("seg1 content changed!\n");
      exit(1);
    }
  }

  for (int i=0; i < old_size; i++) {
    if (in_use2[i] != 0xdd) {
      printf("seg2 content changed!\n");
      exit(1);
    }
  }

  printf("Press Enter to resize in-use segment 1 memory...\n");
  getchar();

  // "Unprotect" the old reserved region first
  if (mprotect(map1_addr + old_size, MAX_SZ/2 - old_size, PROT_READ | PROT_WRITE) == -1) {
    perror("mprotect");
    exit(1);
  }

  if (ftruncate(fd_1, new_size) == -1) {
    perror("fd_1 ftruncate");
    exit(1);
  }

  // Protect the new reserved region
  if (mprotect(map1_addr + new_size, MAX_SZ/2 - new_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }

  printf("Press Enter to resize in-use segment 2 memory...\n");
  getchar();

  if (mprotect(map2_addr + old_size, MAX_SZ/2 - old_size, PROT_READ | PROT_WRITE) == -1) {
    perror("mprotect");
    exit(1);
  }

  if (ftruncate(fd_2, new_size) == -1) {
    perror("fd_2 ftruncate");
    exit(1);
  }

  if (mprotect(map2_addr + new_size, MAX_SZ/2 - new_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }

  size_t check_size = (old_size <= new_size ? old_size : new_size);
  for (int i=0; i < check_size; i++) {
    if (in_use1[i] != 0xee) {
      printf("new seg1 content changed!\n");
      exit(1);
    }
  }

  for (int i=0; i < check_size; i++) {
    if (in_use2[i] != 0xdd) {
      printf("new seg2 content changed!\n");
      exit(1);
    }
  }

  memset(map1_addr, 0xaa, new_size);
  for (int i=0; i < new_size; i++) {
    if (in_use1[i] != 0xaa) {
      printf("new seg1 corrupted!\n");
      exit(1);
    }
  }

  memset(map2_addr, 0xbb, new_size);
  for (int i=0; i < new_size; i++) {
    if (in_use2[i] != 0xbb) {
      printf("new seg2 corrupted!\n");
      exit(1);
    }
  }

  printf("Press Enter to shrink seg 1 to 0...\n");
  getchar();
  if (ftruncate(fd_1, 0) == -1) {
    perror("ftruncate");
    exit(1);
  }
  close(fd_1);

  printf("Press Enter to shrink seg 2 to 0...\n");
  getchar();
  if (ftruncate(fd_2, 0) == -1) {
    perror("ftruncate");
    exit(1);
  }
  close(fd_2);

  printf("Press Enter to unmap first mapping...\n");
  getchar();
  if (munmap(map1_addr, MAX_SZ/2) == -1) {
    perror("munmap");
    exit(1);
  }

  printf("Press Enter to unmap first mapping...\n");
  getchar();
  if (munmap(map2_addr, MAX_SZ/2) == -1) {
    perror("munmap");
    exit(1);
  }
  
  printf("Press Enter to termiante program...\n");
  getchar();
  return 0;
}


Attachments:

  [text/plain] test_hugepage_mappings.c (5.7K, ../73f05b290b584e0581c4a96a33daa77b@huawei.com/3-test_hugepage_mappings.c)
  download | inline:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#include <string.h>
#include <errno.h>


#define HUGE_PG_SZ ((size_t)(2048*1024))
unsigned long long MAX_SZ = (100 * ((size_t)(1024 * 1024) * (size_t)(1024 * 1024))); // 100 TB

int main(int argc, char *argv[]) {
  int fd_1 = -1;
  int fd_2 = -1;
  size_t old_size = atoi(argv[1]) * HUGE_PG_SZ;
  size_t new_size = atoi(argv[2]) * HUGE_PG_SZ;

  // Probe for a valid address that can grow up to MAX_SZ
  void *probe = mmap(NULL, MAX_SZ,
                     PROT_READ | PROT_WRITE,
                     MAP_ANONYMOUS | MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, -1, 0);
  if (probe == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  if (munmap(probe, MAX_SZ) == -1) {
    perror("munmap");
    exit(1);
  }

  /*

  |----- Memory mapping (MAP_NORESERVE) #1 -----|----- Memory mapping (MAP_NORESERVE) #2 -----|
  |-- In-use region 1 --|-- Reserved region 1 --|-- In-use region 2 --|-- Reserved region 2 --|

  */

  // Create anon mapping #1 that's backed by an anon file fd_1
  fd_1 = memfd_create("test_hugepage_map1", MFD_HUGETLB | MFD_HUGE_2MB | MFD_CLOEXEC);
  if (fd_1 == -1) {
    perror("memfd_create");
    exit(1);
  }

  void *map1_addr = mmap(probe, MAX_SZ/2,
                         PROT_READ | PROT_WRITE,
                         MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, fd_1, 0);
  if (map1_addr == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  // Create in-use region 1
  if (ftruncate(fd_1, old_size) == -1) {
    perror("ftruncate");
    exit(1);
  }

  // The difference between ftruncate and fallocate is that ftruncate does not actually
  // allocate any memory yet (rather it's allocated on "first touch") but fallocate
  // does and also initializes the memory with zeros.
  /*if (fallocate(fd_1, 0, 0, old_size) == -1) {
    perror("fallocate");
    exit(1);
  }*/

  // Optionally, we can protect the reserved region just to be safe
  if (mprotect(map1_addr + old_size, MAX_SZ/2 - old_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }


  // Create anon mapping #2 that's backed by an anon file fd_2
  fd_2 = memfd_create("test_hugepage_map2", MFD_HUGETLB | MFD_HUGE_2MB | MFD_CLOEXEC);
  if (fd_2 == -1) {
    perror("memfd_create");
    exit(1);
  }

  void *map2_addr = mmap(probe + MAX_SZ/2, MAX_SZ/2,
                         PROT_READ | PROT_WRITE,
                         MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, fd_2, 0);
  if (map2_addr == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }

  // Create in-use region 2
  if (ftruncate(fd_2, old_size) == -1) {
    perror("ftruncate");
    exit(1);
  }
  /*if (fallocate(fd_2, 0, 0, old_size) == -1) {
    perror("fallocate");
    exit(1);
  }*/

  if (mprotect(map2_addr + old_size, MAX_SZ/2 - old_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }

  unsigned char *in_use1 = (unsigned char*)map1_addr;
  unsigned char *in_use2 = (unsigned char*)map2_addr;

  printf("Press Enter to fill in-use segment 1 memory...\n");
  getchar();
  memset(map1_addr, 0xee, old_size);

  printf("Press Enter to fill in-use segment 2 memory...\n");
  getchar();
  memset(map2_addr, 0xdd, old_size);

  for (int i=0; i < old_size; i++) {
    if (in_use1[i] != 0xee) {
      printf("seg1 content changed!\n");
      exit(1);
    }
  }

  for (int i=0; i < old_size; i++) {
    if (in_use2[i] != 0xdd) {
      printf("seg2 content changed!\n");
      exit(1);
    }
  }

  printf("Press Enter to resize in-use segment 1 memory...\n");
  getchar();

  // "Unprotect" the old reserved region first
  if (mprotect(map1_addr + old_size, MAX_SZ/2 - old_size, PROT_READ | PROT_WRITE) == -1) {
    perror("mprotect");
    exit(1);
  }

  if (ftruncate(fd_1, new_size) == -1) {
    perror("fd_1 ftruncate");
    exit(1);
  }

  // Protect the new reserved region
  if (mprotect(map1_addr + new_size, MAX_SZ/2 - new_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }

  printf("Press Enter to resize in-use segment 2 memory...\n");
  getchar();

  if (mprotect(map2_addr + old_size, MAX_SZ/2 - old_size, PROT_READ | PROT_WRITE) == -1) {
    perror("mprotect");
    exit(1);
  }

  if (ftruncate(fd_2, new_size) == -1) {
    perror("fd_2 ftruncate");
    exit(1);
  }

  if (mprotect(map2_addr + new_size, MAX_SZ/2 - new_size, PROT_NONE) == -1) {
    perror("mprotect");
    exit(1);
  }

  size_t check_size = (old_size <= new_size ? old_size : new_size);
  for (int i=0; i < check_size; i++) {
    if (in_use1[i] != 0xee) {
      printf("new seg1 content changed!\n");
      exit(1);
    }
  }

  for (int i=0; i < check_size; i++) {
    if (in_use2[i] != 0xdd) {
      printf("new seg2 content changed!\n");
      exit(1);
    }
  }

  memset(map1_addr, 0xaa, new_size);
  for (int i=0; i < new_size; i++) {
    if (in_use1[i] != 0xaa) {
      printf("new seg1 corrupted!\n");
      exit(1);
    }
  }

  memset(map2_addr, 0xbb, new_size);
  for (int i=0; i < new_size; i++) {
    if (in_use2[i] != 0xbb) {
      printf("new seg2 corrupted!\n");
      exit(1);
    }
  }

  printf("Press Enter to shrink seg 1 to 0...\n");
  getchar();
  if (ftruncate(fd_1, 0) == -1) {
    perror("ftruncate");
    exit(1);
  }
  close(fd_1);

  printf("Press Enter to shrink seg 2 to 0...\n");
  getchar();
  if (ftruncate(fd_2, 0) == -1) {
    perror("ftruncate");
    exit(1);
  }
  close(fd_2);

  printf("Press Enter to unmap first mapping...\n");
  getchar();
  if (munmap(map1_addr, MAX_SZ/2) == -1) {
    perror("munmap");
    exit(1);
  }

  printf("Press Enter to unmap first mapping...\n");
  getchar();
  if (munmap(map2_addr, MAX_SZ/2) == -1) {
    perror("munmap");
    exit(1);
  }
  
  printf("Press Enter to termiante program...\n");
  getchar();
  return 0;
}

view thread (167+ messages)  latest in thread

Message-ID: <73f05b290b584e0581c4a96a33daa77b@huawei.com>
Permalink:  ../73f05b290b584e0581c4a96a33daa77b@huawei.com/
Also on:    postgresql.org/message-id/73f05b290b584e0581c4a96a33daa77b@huawei.com

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: Jack.Ng@huawei.com, ashutosh.bapat.oss@gmail.com, 9erthalion6@gmail.com, robertmhaas@gmail.com, jakkuniku@gmail.com
  Subject: RE: Changing shared_buffers without restart
  In-Reply-To: <73f05b290b584e0581c4a96a33daa77b@huawei.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox