일주일 전에 pg_walviz로 WAL segment 내부를 들여다봤는데, 같은 저자가 이번엔 공유 메모리를 열었어요. Bertrand Drouvot(베르트랑 드루보)이 2026년 8월 20일 pg_shmemviz v0.1.0-beta.1을 공개했습니다.

두 도구의 성격이 닮았습니다. 이미 있는 뷰로도 요약은 볼 수 있는데, 정작 “그게 메모리 어디에 어떤 모양으로 놓여 있나"는 안 보인다는 문제를 같은 방식으로 풉니다. 스냅샷을 떠서 브라우저에서 바이트 단위로 걸어 다니게 하는 방식입니다.

pg_shmem_allocations는 어디까지 보여 주나

먼저 기존 뷰의 한계를 실측으로 확인했습니다. PostgreSQL 18 컨테이너를 기본 설정으로 띄우고 pg_shmem_allocations를 조회했습니다.

$ docker run -d --name shmemtest -e POSTGRES_PASSWORD=pw postgres:18
$ docker exec shmemtest psql -U postgres -Atc "show shared_buffers"
128MB
select name, off, size, allocated_size
  from pg_shmem_allocations
 order by size desc limit 8;
         name         |    off    |   size    | allocated_size
----------------------+-----------+-----------+----------------
 Buffer Blocks        |   6785664 | 134221824 |      134221824
 <anonymous>          |           |   4747776 |        4747776
 XLOG Ctl             |     60928 |   4208200 |        4208256
 AioHandleIOV         | 150482816 |   2850816 |        2850816
                      | 154760064 |   2280576 |        2280576
 AioHandle            | 148879232 |   1603584 |        1603584
 AioHandleData        | 153333632 |   1425408 |        1425408
 Buffer Descriptors   |   5737088 |   1048576 |        1048576

전체는 75개 항목, 합계 150MB였습니다. 여기서 볼 수 있는 것과 볼 수 없는 것이 꽤 선명하게 갈립니다.

보이는 것은 이름, 시작 오프셋, 요청 크기, 실제 할당 크기입니다. XLOG Ctl이 4,208,200바이트를 요청했는데 4,208,256바이트가 할당된 것을 보면 56바이트가 정렬 때문에 붙었다는 사실까지는 읽힙니다. PostgreSQL 18에서 비동기 I/O가 들어오면서 AioHandle, AioHandleIOV, AioHandleData 세 항목이 합쳐 5.8MB를 차지하는 것도 확인됩니다. 비동기 I/O 글에서 다룬 io_uring 구조가 메모리에서 이 정도 자리를 쓴다는 뜻입니다.

안 보이는 것이 문제입니다. 위 출력에서 이름 칸이 빈 행이 하나 있는데, 이건 아직 아무에게도 배정되지 않은 여유 공간입니다. <anonymous>는 이름 없이 잡힌 익명 할당이라 오프셋조차 안 나옵니다. 그리고 XLOG Ctl 안에 XLogCtlData 구조체의 어떤 필드가 몇 번째 바이트에 앉아 있는지, 필드 사이에 컴파일러가 끼워 넣은 padding이 몇 바이트인지는 이 뷰의 관심사가 아닙니다.

정리하면 이렇습니다.

알고 싶은 것pg_shmem_allocationspg_shmemviz
할당 이름과 크기지원지원
요청 크기 대 실제 크기 차이지원지원
할당 사이 빈 구간의 물리적 위치부분 (이름 없는 행)지원
C 구조체의 필드별 오프셋과 값지원
필드 사이 컴파일러 padding지원
포인터가 가리키는 대상 영역지원
페이지의 NUMA 노드 배치pg_shmem_allocations_numa지원 (시각화)
두 시점 사이 차이 비교수동지원

실행 중인 서버에 붙지 않고 구조체를 읽는 방법

여기가 이 도구에서 가장 흥미로운 부분입니다. 공유 메모리의 바이트 배열을 읽는 것 자체는 어렵지 않습니다. 어려운 건 그 바이트가 무슨 구조체의 어느 필드인지 알아내는 일입니다. 그 정보는 소스 코드에만 있고 실행 중인 서버의 메모리에는 없습니다.

pg_shmemviz는 이 문제를 postgres 실행 파일의 DWARF 디버그 정보로 해결합니다. 컴파일된 바이너리에는 각 구조체의 필드 이름, 타입, 오프셋이 DWARF 형식으로 들어 있습니다. macOS에서는 LLDB, 그 외 환경에서는 GDB를 써서 이 메타데이터를 읽습니다. 중요한 건 디버거를 실행 중인 서버에 attach하지 않는다는 점입니다. 실행 파일의 메타데이터만 읽습니다.

flowchart TD
    A[capture 실행] --> B[공유 메모리 순차 복사]
    A --> C[postgres 바이너리]
    C --> D[DWARF 정보 추출
LLDB 또는 GDB] B --> E[스냅샷 디렉터리] D --> E E --> F[serve: 로컬 브라우저]

복사 단계에는 lock을 걸지 않습니다. 덕분에 서버를 멈추지 않지만, 대가가 있습니다. 스냅샷 안의 필드들이 서로 다른 순간의 값일 수 있습니다. 저자도 이 점을 문서에 명시했습니다. 그러니 “이 두 카운터의 차이가 정확히 몇인가"를 따지는 용도로는 맞지 않고, 구조와 배치를 파악하는 용도입니다.

설치와 사용

extension과 CLI 두 부분으로 되어 있습니다.

cd ~/pg_shmemviz
make PG_CONFIG=/path/to/postgres-install/bin/pg_config
make PG_CONFIG=/path/to/postgres-install/bin/pg_config install

psql -d postgres -c 'CREATE EXTENSION pg_shmemviz'

스냅샷을 뜨는 명령입니다.

~/pg_shmemviz/bin/pg_shmemviz capture \
  --pg-config /path/to/postgres-install/bin/pg_config \
  --dbname postgres \
  /path/to/new-snapshot

뜬 스냅샷을 브라우저로 봅니다. 기본값은 127.0.0.1:8765이고 로컬 브라우저가 열립니다.

~/pg_shmemviz/bin/pg_shmemviz serve /path/to/new-snapshot

원격 서버에서 뜬 스냅샷을 SSH 포트 포워딩으로 볼 때는 브라우저 자동 실행을 끕니다.

~/pg_shmemviz/bin/pg_shmemviz serve --no-open --port 8765 /path/to/new-snapshot

화면이 보여 주는 것

뷰가 여러 개인데 서로 연동됩니다. 한쪽에서 할당을 고르면 다른 쪽이 같은 지점을 따라갑니다.

공유 메모리 맵은 main segment와 DSM, DSA 영역을 통틀어 이름 있는 할당, padding, 미사용 구간을 늘어놓습니다. 위 실측에서 이름 칸이 비어 있던 2.2MB가 어디에 어떤 이웃과 붙어 있는지가 여기서 드러납니다.

Structure Fields 패널이 이 도구의 핵심입니다. 중첩된 C 구조체를 펼쳐 필드별 오프셋과 값, 컴파일러가 끼운 padding, 배열의 stride padding을 보여 줍니다. 경계를 알 수 있는 포인터는 가리키는 영역을 참조 구간으로 표시합니다. 통계, WAL, 프로세스 배열, SLRU, dynahash, DSM registry 같은 PostgreSQL 특유의 구조에는 전용 해석이 들어가 있습니다.

Physical Bytes 뷰는 주소, 오프셋, 값, 어느 구조체 필드에 속하는지, 어느 NUMA 노드에 놓였는지를 함께 보여 주는 바이트 창입니다. Buffer Cache 뷰는 선택 사항인데, buffer별 식별자와 database, relation, fork, block 번호, 그리고 그 buffer를 pin하고 있는 backend까지 나옵니다. pg_buffercache로 보던 내용을 물리적 배치 위에 겹쳐 놓은 셈입니다.

NUMA 노드가 여러 개인 장비에서는 페이지 배치를 그림으로 봅니다. pg_shmem_allocations_numa 뷰가 숫자로 알려 주던 것을 눈으로 확인하는 용도입니다.

스냅샷 두 개를 비교한다

이 기능이 실무 관점에서 가장 쓸모 있어 보입니다. 서로 다른 시점의 스냅샷 두 개를 나란히 열어 할당 단위, 필드 단위, 바이트 단위로 차이를 봅니다.

~/pg_shmemviz/bin/pg_shmemviz serve \
  /path/to/before-snapshot \
  /path/to/after-snapshot

설정 하나를 바꿨을 때 공유 메모리가 실제로 어떻게 달라지는지 확인하는 데 쓸 수 있습니다. 위 실측에서 PostgreSQL 18의 AIO 관련 세 할당이 5.8MB를 차지했는데, io_method를 바꾸기 전후로 스냅샷을 떠서 비교하면 그 5.8MB의 내부 구성이 어떻게 변하는지가 필드 단위로 보일 것입니다.

운영 인스턴스에서는 쓰지 않는다

저자가 문서 앞쪽에 강하게 못 박은 부분이라 그대로 옮깁니다.

Do not run pg_shmemviz on a production PostgreSQL instance.

이유가 몇 겹입니다. 스냅샷은 공유 메모리 전체를 복사한 파일이라 크고, 그 안에 실제 데이터가 그대로 들어갑니다. buffer에 올라온 테이블 내용이 파일로 떠지는 셈이니 민감 정보가 그대로 흘러나갑니다. viewer에는 인증도, 권한 검사도, TLS도 없습니다. loopback 밖으로 내보내면 안 됩니다.

구조체 해석에는 캡처한 서버가 쓰던 것과 정확히 같은 postgres 실행 파일이 필요합니다. 빌드가 다르면 필드 오프셋이 어긋나 엉뚱한 값을 읽습니다. 검증은 PostgreSQL 20devel 기준으로 되어 있고, 어느 버전까지 되는지는 저장소 README를 봐야 합니다. 제가 위에서 실측한 PostgreSQL 18 컨테이너 이미지에는 디버그 정보가 없으니, 실제로 붙여 보려면 디버그 심볼을 켜서 직접 빌드한 인스턴스가 필요합니다.

어디에 쓰면 좋을까

용도가 좁습니다. 운영 진단 도구가 아니고, extension이나 코어 패치를 개발하면서 “내가 잡은 공유 메모리 구조가 실제로 어떻게 배치됐나"를 확인하는 도구입니다. 그리고 학습 자료로서의 가치가 따로 있습니다. XLogCtlDataPGPROC 배열이 메모리에서 어떤 모양인지 소스만 읽어서 상상하던 것을 눈으로 확인하는 경험은 소스 독해 속도를 꽤 올려 줍니다.

PostgreSQL 내부를 3D 도시로 걸어 본 글에서 시각화 도구가 학습에 어떤 도움이 되는지 이야기했는데, pg_shmemviz는 그보다 훨씬 실무 쪽에 가깝습니다. 비유가 아니라 실제 주소와 실제 바이트를 보여 주니까요.

pg_walviz와 pg_shmemviz가 한 주 간격으로 나왔습니다. 저는 이 흐름이 반갑습니다. PostgreSQL 내부는 소스를 읽을 수 있는 사람에게만 열려 있었는데, 그 문턱을 낮추는 도구가 늘고 있어요.

참고