Innovating today, leading tomorrow

OpenSQL_Internals
[OpenSQL] PostgreSQL의 Background Process 관리

[OpenSQL] PostgreSQL의 Background Process 관리

PostgreSQL 은 하나의 데이터베이스 클러스터를 운영하기 위한 여러 프로세스로 구성되어 있습니다. PostgreSQL 프로세스의 기능 및 아키텍쳐에 대한 자세한 내용은 저희 블로그의 “PostgreSQL의 프로세스와 메모리 아키텍처” 게시글을 참고해 주세요. postgres 프로세스는 데이터베이스 클러스터를 운영하기 위해 여러 역할을 수행하지만 모든 백엔드 프로세스, 백그라운드 프로세스를 관리하는 역할도 수행합니다. 백엔드 프로세스, 백그라운드 프로세스가 비정상적으로 종료되었을 때 postgres 프로세스가 이를 인지하고 적절한 처리와 함께 해당 프로세스를 재생성합니다. 이번 장에서는 postgres 프로세스가 자식 프로세스를 어떻게 관리하는지 자세히 알아보도록 하겠습니다. postmaster라는 용어도 많이 사용되지만 PostgreSQL 공식 문서에서 postgres 프로세스라는 용어를 사용하므로 본 글에서는 postgres 프로세스라고 표기하도록 하겠습니다.

postgres 는 모든 백엔드, 백그라운드 프로세스의 부모 프로세스이다.

PostgreSQL을 구성하고 있는 모든 백엔드, 백그라운드 프로세스는 postgres를 fork 하여 생성됩니다. 다음 명령어를 통하여 직접 확인해 보도록 하겠습니다.

root@a4ac31d234ff:/# ps -fu postgres
UID PID PPID C STIME TTY TIME CMD
postgres 1 0 0 14:20 pts/0 00:00:00 bash -i /scripts/entrypoint.sh
postgres 24 1 0 14:20 pts/0 00:00:00 postgres
postgres 25 24 0 14:20 ? 00:00:00 postgres: logger
postgres 27 24 0 14:20 ? 00:00:00 postgres: checkpointer
postgres 28 24 0 14:20 ? 00:00:00 postgres: background writer
postgres 29 24 0 14:20 ? 00:00:00 postgres: walwriter
postgres 30 24 0 14:20 ? 00:00:00 postgres: autovacuum launcher
postgres 31 24 0 14:20 ? 00:00:00 postgres: archiver
postgres 32 24 0 14:20 ? 00:00:00 postgres: stats collector
postgres 33 24 0 14:20 ? 00:00:00 postgres: logical replication launcher

백엔드 및 백그라운드 프로세스의 PPID(parent process ID) 는 모두 24로 postgres를 부모 프로세스로 가지는 것을 확인할 수 있습니다. 그렇다면 postgres 프로세스는 어떻게 자식 프로세스의 상태를 확인하고 있을까요?

postgres 프로세스 내에는 ServerLoop라는 이벤트 루프 함수가 존재합니다. ServerLoop 함수는 이벤트가 발생할 때까지 대기하고, 이벤트가 발생하면 해당 이벤트를 처리하고 다시 대기합니다. ServerLoop 함수 내부에는 postgres 프로세스가 수행하는 여러 동작이 구현되어있습니다. client connection 요청에 대한 처리, 새로운 자식 프로세스를 fork 하는 동작 등 다양한 작업을 수행합니다. 하지만 ServerLoop 에서 처리해야 하는 중요한 기능 중 하나는 fork 된 모든 자식 프로세스에 대한 상태를 확인 하고 이상이 있을 경우 이를 처리하는 것 입니다.

    if (pmState == PM_RUN || pmState == PM_RECOVERY ||
        pmState == PM_HOT_STANDBY || pmState == PM_STARTUP)
    {
        if (CheckpointerPID == 0)
            CheckpointerPID = StartCheckpointer();
        if (BgWriterPID == 0)
            BgWriterPID = StartBackgroundWriter();
    }

    /*
     * Likewise, if we have lost the walwriter process, try to start a new
     * one. But this is needed only in normal operation (else we cannot
     * be writing any new WAL).
     */
    if (WalWriterPID == 0 && pmState == PM_RUN)
        WalWriterPID = StartWalWriter();

위의 소스코드는 postmaster.c 내의 ServerLoop 함수의 일부입니다. CheckpointerPID, BgWriterPID, WalWriterPID 등은 각각 백그라운드 프로세스의 PID(process ID)를 나타내는 정적 변수입니다. 이 프로세스 들은 모두 postgres 의 자식 프로세스입니다. postgres 프로세스는 ServerLoop 함수 내에서 지속적으로 PID를 확인하여 자식 프로세스의 유무를 확인하며 자식 프로세스가 존재하지 않을 때 이를 다시 시작하도록 하는 동작을 수행합니다.

시그널과 프로세스

PostgreSQL 의 프로세스들은 별 일이 없다면 정상적으로 동작합니다. 하지만 각 프로세스들은 예기치 못한 오류나 이벤트가 발생할 수 있는데 PostgreSQL에서는 시그널(signal)을 이용하여 이를 처리하곤 합니다. 시그널이란 소프트웨어 인터럽트(software interrupt)의 일종으로 오류, 종료, 사용자 입력 등의 이벤트가 발생했음을 프로세스에게 알려주는 것입니다. 대표적인 시그널로 SIGINT가 있는데 사용자가 Ctrl+C 를 눌렀을 때 보내지는 시그널이며 주로 강제로 프로그램을 종료하고 싶을 때 사용합니다. Unix 시그널의 종류는 kill -l 명령어를 통하여 확인 가능합니다.

시그널 핸들러(signal handler)를 등록하여 프로세스가 시그널을 수신할 때 호출할 함수를 지정할 수 있습니다. 시그널이 전달해주는 이벤트는 주로 프로세스에 치명적인 경우가 많으므로 시그널을 받았을 때 이를 적절히 처리하는 것은 중요합니다. PostgreSQL 의 여러 프로세스도 시그널 핸들러를 이용하여 프로세스가 안정적으로 운영되도록 합니다.

pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, SignalHandlerForShutdownRequest);

위의 소스코드는 PostgreSQL 의 bgwriter.c 소스코드의 일부입니다. pgsignal 함수는 시그널에 대한 시그널 핸들러를 등록할 때 사용하는 함수입니다. 즉, 위와 같이 등록해 놓으면 background writer 프로세스 내에서 SIGINT 시그널이 발생하면 SIG_IGN 가 호출되고 SIGTERM 시그널이 발생했을 때 내부적으로 SignalHandlerForShutdownRequest를 호출하게 되게 됩니다. 각 시그널 핸들러 동작에 대해 알아보겠습니다.

SIG_IGN 는 PostgreSQL 내부에서 시그널을 무시하라는 의미로 사용됩니다. 즉, SIGINT 시그널을 받았을 때 아무런 동작도 수행하지 않습니다. 그렇다면 SignalHandlerForShutdown는 어떨까요? 해당 핸들러는 내부적으로 프로세스를 정상 종료 시킬 때 사용하는 함수입니다. 프로세스가 점유하고 있는 모든 리소스를 해제하고 이전에 할당된 모든 메모리를 반환하는 정상 종료를 수행하게 됩니다.

그렇다면 background writer 프로세스를 예시로 백그라운드 프로세스에 시그널을 보내면 어떻게 동작하는지 알아보도록 하겠습니다.

백그라운드 프로세스 강제 종료

root@a4ac31d234ff:/# ps -fu postgres
UID PID PPID C STIME TTY TIME CMD
postgres 1 0 0 14:20 pts/0 00:00:00 bash -i /scripts/entrypoint.sh
postgres 24 1 0 14:20 pts/0 00:00:00 postgres
postgres 25 24 0 14:20 ? 00:00:00 postgres: logger
postgres 27 24 0 14:20 ? 00:00:00 postgres: checkpointer
postgres 28 24 0 14:20 ? 00:00:00 postgres: background writer
postgres 29 24 0 14:20 ? 00:00:00 postgres: walwriter
postgres 30 24 0 14:20 ? 00:00:00 postgres: autovacuum launcher
postgres 31 24 0 14:20 ? 00:00:00 postgres: archiver
postgres 32 24 0 14:20 ? 00:00:00 postgres: stats collector
postgres 33 24 0 14:20 ? 00:00:00 postgres: logical replication launche 00:00:00 postgres: walwriter
postgres 30 24 0 14:20 ? 00:00:00 postgres: autovacuum launcher
postgres 31 24 0 14:20 ? 00:00:00 postgres: archiver
postgres 32 24 0 14:20 ? 00:00:00 postgres: stats collector
postgres 33 24 0 14:20 ? 00:00:00 postgres: logical replication launcher

우리는 위와 같은 PostgreSQL 서버가 있을 때 background writer 프로세스가 각각 SIGINT, SIGTERM , SIGKILL 시그널에 어떻게 동작하는지 확인해보도록 하겠습니다.

SIGINT

root@a4ac31d234ff:/# kill -2 28
root@a4ac31d234ff:/# ps -fu postgres
UID PID PPID C STIME TTY TIME CMD
postgres 1 0 0 14:20 pts/0 00:00:00 bash -i /scripts/entrypoint.sh
postgres 24 1 0 14:20 pts/0 00:00:00 postgres
postgres 25 24 0 14:20 ? 00:00:00 postgres: logger
postgres 27 24 0 14:20 ? 00:00:00 postgres: checkpointer
postgres 28 24 0 14:20 ? 00:00:00 postgres: background writer
postgres 29 24 0 14:20 ? 00:00:00 postgres: walwriter
postgres 30 24 0 14:20 ? 00:00:00 postgres: autovacuum launcher
postgres 31 24 0 14:20 ? 00:00:00 postgres: archiver
postgres 32 24 0 14:20 ? 00:00:00 postgres: stats collector
postgres 33 24 0 14:20 ? 00:00:00 postgres: logical replication launcher

kill 명령어를 통하여 임의로 background writer 프로세스에 SIGINT 시그널을 주었습니다. kill 명령어는 Kill [option] [PID] 의 형태로 2는 SIGINT 의 옵션 번호이고 background writer 의 PID 인 28을 주었습니다. ps -fu postgres 명령어로 확인 결과 아무런 변화가 없습니다. 즉, SIG_IGN 의 의미처럼 SIGINT 시그널에 대하여 아무런 동작도 수행하지 않습니다. 그렇다면 SIGTERM 시그널은 어떨까요?

SIGTERM

root@a4ac31d234ff:/# kill -15 28
root@a4ac31d234ff:/# ps -fu postgres
UID PID PPID C STIME TTY TIME CMD
postgres 1 0 0 14:20 pts/0 00:00:00 bash -i /scripts/entrypoint.sh
postgres 24 1 0 14:20 pts/0 00:00:00 postgres
postgres 25 24 0 14:20 ? 00:00:00 postgres: logger
postgres 27 24 0 14:20 ? 00:00:00 postgres: checkpointer
postgres 29 24 0 14:20 ? 00:00:00 postgres: walwriter
postgres 30 24 0 14:20 ? 00:00:00 postgres: autovacuum launcher
postgres 31 24 0 14:20 ? 00:00:00 postgres: archiver
postgres 32 24 0 14:20 ? 00:00:00 postgres: stats collector
postgres 33 24 0 14:20 ? 00:00:00 postgres: logical replication launcher
postgres 28528 24 0 15:06 ? 00:00:00 postgres: background writer

kill 명령어의 옵션으로 SIGTERM 의 옵션번호인 15 를 주어 background writer 프로세스에게 SIGTERM 시그널을 주었습니다. ps -fu postgres 명령어로 확인해보면 background writer 프로세스는 존재하지만 PID 는 28528로 바뀌었으며 PPID 는 여전히 24 인 것을 확인할 수 있습니다. 즉, background writer 프로세스는 SIGTERM 시그널을 받아 정상종료되었고 이후 postgres 프로세스에 의해 다시 fork 되었다는 것을 알 수 있습니다. ServerLoop 에서 background writer 프로세스가 없다는 것을 인지하고 해당 프로세스를 복구한 것이지요. 그렇다면 SIGKILL 시그널은 어떨까요?

SIGKILL

root@a4ac31d234ff:/# kill -9 28528
root@a4ac31d234ff:/# ps -fu postgres
UID PID PPID C STIME TTY TIME CMD
postgres 1 0 0 14:20 pts/0 00:00:00 bash -i /scripts/entrypoint.sh
postgres 24 1 0 14:20 pts/0 00:00:00 postgres
postgres 25 24 0 14:20 ? 00:00:00 postgres: logger
postgres 30525 24 0 15:10 ? 00:00:00 postgres: checkpointer
postgres 30526 24 0 15:10 ? 00:00:00 postgres: background writer
postgres 30527 24 0 15:10 ? 00:00:00 postgres: walwriter
postgres 30528 24 0 15:10 ? 00:00:00 postgres: autovacuum launcher
postgres 30529 24 0 15:10 ? 00:00:00 postgres: archiver
postgres 30530 24 0 15:10 ? 00:00:00 postgres: stats collector
postgres 30531 24 0 15:10 ? 00:00:00 postgres: logical replication launcher

kill 명령어의 옵션으로 SIGKILL 의 옵션번호인 9 를 주어 background writer 프로세스에게 SIGKILL 시그널을 주었습니다. 확인 결과 logger 프로세스 를 제외한 모든 백그라운드 프로세스들이 종료 후 다시 생성되었습니다. 왜 일까요?

2023-05-09 20:40:04.090 KST [1] LOG: background writer process (PID 1607) was terminated by signal 9: Killed
2023-05-09 20:40:04.090 KST [1] LOG: terminating any other active server processes
2023-05-09 20:40:04.091 KST [1] LOG: all server processes terminated; reinitializing

kill -9 명령어는 프로세스를 강제 종료시키고 해당 프로세스가 사용하고 있던 자원들을 즉시 해제시킵니다. postgres 프로세스는 자식프로세스가 죽게되면 SIGCHLD 시그널을 받게되고 이에 따른 시그널 핸들러를 호출합니다. 해당 시그널핸들러는 postgres 프로세스의 자식프로세스가 비정상 종료되었을 때 HandleChildCrash 함수를 호출합니다. HandleChildCrash 함수는 모든 자식프로세스의 상태를 확인 후 비정상종료된 자식프로세스가 있다면 다른 백그라운드 프로세스를 종료시키고 재가동 할 수 있도록 합니다.

모든 백그라운드 프로세스를 종료시키는 이유는 백그라운드 프로세스들끼리의 공유리소스를 가지고 있는데 비정상 종료된 백그라운드 프로세스로 인하여 다른 백그라운드 프로세스도 영향을 받을 수 있기 때문입니다. 따라서 postgres 프로세스는 백그라운드 프로세스들의 안정성을 보장하기 위하여 영향을 받을 수 있는 모든 백그라운드 프로세스를 종료시키고 재가동 시킵니다. 여기서 주의해야할 점은 백그라운드 프로세스가 비정상 종료될 시 백엔드 프로세스 역시 함께 종료된다는 점입니다. 백엔드 프로세스는 재가동되지 않습니다.

결론

우리는 이번장을 통하여 postgres 프로세스가 자식 프로세스의 상태를 확인하고 관리하는지 알아보았습니다. 또한 background writer 프로세스를 예로 백그라운드 프로세스가 시그널을 받았을 때 어떤 동작을 수행하는지 알아봤습니다. 백그라운드 프로세스는 PostgreSQL 를 원활하게 운영하고 유지 보수시키는데 필수적인 역할을 수행합니다. 백그라운드 프로세스는 시그널을 받았을 때 각자의 역할에 따라 각자 다른 시그널 핸들러 처리를 수행합니다. 여러 백그라운드 프로세스에 다양한 시그널을 보내고 이에 대하여 어떻게 동작하는지 확인해 보세요.

지금까지 PostgreSQL의 Background Process 관리에 관해 알아보았습니다

PostgreSQL의 Parallel Query (1)을 바로 이어서 확인해보세요!

광고성 정보 수신

개인정보 수집, 활용 목적 및 기간

(주)티맥스티베로의 개인정보 수집 및 이용 목적은 다음과 같습니다.
내용을 자세히 읽어보신 후 동의 여부를 결정해 주시기 바랍니다.

  • 수집 목적: 티맥스티베로 뉴스레터 발송 및 고객 관리
  • 수집 항목: 성함, 회사명, 회사 이메일, 연락처, 부서명, 직급, 산업, 담당업무, 관계사 여부, 방문 경로
  • 보유 및 이용 기간: 동의 철회 시까지

※ 위 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
※ 필수 수집 항목에 대한 동의를 거부하는 경우 뉴스레터 구독이 제한될 수 있습니다.

개인정보의 처리 위탁 정보
  • 업체명: 스티비 주식회사
  • 위탁 업무 목적 및 범위: 광고가 포함된 뉴스레터 발송 및 수신자 관리
 

개인정보 수집 및 이용

개인정보 수집, 활용 목적 및 기간

(주)티맥스티베로의 개인정보 수집 및 이용 목적은 다음과 같습니다. 내용을 자세히 읽어보신 후 동의 여부를 결정해 주시기 바랍니다.

  • 수집 목적: 티맥스티베로 뉴스레터 발송 및 고객 관리
  • 수집 항목: 성함, 회사명, 회사 이메일, 연락처, 부서명, 직급, 산업, 담당업무, 관계사 여부, 방문 경로
  • 보유 및 이용 기간: 동의 철회 시까지

※ 위 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
※ 필수 수집 항목에 대한 동의를 거부하는 경우 뉴스레터 구독이 제한될 수 있습니다.

개인정보의 처리 위탁 정보

  • 업체명: 스티비 주식회사
  • 위탁 업무 목적 및 범위: 광고가 포함된 뉴스레터 발송 및 수신자 관리
  •