PostgreSQL에서 대용량 테이블에 인덱스를 생성할 때 '메모리 부족' 오류가 발생하는 경우가 있습니다. 특히 shared_buffers나 maintenance_work_mem과 같은 메모리 관련 설정값을 충분히 할당했음에도 불구하고 이러한 문제가 발생하면 혼란스러울 수 있습니다.
다음은 6GB 크기의 테이블에 인덱스를 생성하려다 메모리 부족 오류를 겪은 실제 사례와 해당 오류를 진단하고 해결하는 과정입니다.
현재 PostgreSQL 메모리 설정 확인
먼저, 현재 PostgreSQL 인스턴스의 주요 메모리 관련 설정값을 확인합니다.
qs=> SHOW shared_buffers;
shared_buffers
----------------
12GB
(1 row)
qs=> SHOW work_mem;
work_mem
----------
4MB
(1 row)
qs=> SHOW maintenance_work_mem;
maintenance_work_mem
----------------------
8GB
(1 row)
이 설정들은 각각 다음과 같은 역할을 합니다:
shared_buffers: PostgreSQL이 사용하는 공유 메모리 영역으로, 주로 캐싱에 사용됩니다.work_mem: 개별 정렬(sort) 또는 해시(hash) 작업에 사용되는 메모리입니다. 기본값이 낮게 설정된 경우가 많습니다.maintenance_work_mem: 인덱스 생성(CREATE INDEX), VACUUM, ALTER TABLE과 같은 유지보수 작업에 사용되는 메모리입니다. 인덱스 생성 시 중요한 설정입니다.
위 사례에서는 shared_buffers에 12GB, maintenance_work_mem에 8GB가 할당되어 있어, 인덱스 생성에 충분해 보입니다.
인덱스 생성 시 발생한 오류
다음은 `ACT_GE_BYTEARRAY` 테이블의 `NAME_` 컬럼에 B-tree 인덱스를 생성하려 했을 때 발생한 오류 메시지입니다.
qs=> CREATE INDEX ACT_IDX_BYTEARRAY_NAME ON qs.ACT_GE_BYTEARRAY USING btree (NAME_);
오류: 메모리 용량 부족
상세: 메모리 컨텍스트 "TupleSort main"에서 크기 9바이트 요청 실패.
배경: 병렬 작업자 프로세스
오류 메시지에서 "메모리 용량 부족"과 함께 "TupleSort main"이라는 특정 메모리 컨텍스트가 언급되고, "병렬 작업자 프로세스"에서 발생했음이 명시되었습니다. 이는 인덱스 생성을 위한 정렬 작업 도중 메모리가 고갈되었음을 시사합니다.
시스템 메모리 상태 확인
PostgreSQL 설정이 충분해 보여도, 실제 운영체제 수준에서 사용 가능한 메모리가 부족한지 확인하는 것이 중요합니다.
# free -h
total used free shared buff/cache available
Mem: 31G 759M 25G 739M 5.4G 29G
Swap: 4.9G 0B 4.9G
시스템 총 메모리 31GB 중 25GB가 사용 가능하며, 스왑은 전혀 사용되지 않았습니다. 이는 시스템 자체에는 여유 메모리가 충분하다는 것을 의미합니다. 따라서 문제는 PostgreSQL 내부 설정 또는 동작 방식에 있을 가능성이 높습니다.
PostgreSQL 메모리 컨텍스트 분석
오류 발생 직후 PostgreSQL 서버 로그나 pg_backend_memory_contexts() 함수를 통해 메모리 사용 현황을 자세히 살펴보면 문제의 원인을 파악할 수 있습니다. 다음은 메모리 컨텍스트 덤프의 일부입니다.
TopMemoryContext: 384728 total in 11 blocks; 59376 free (8 chunks); 325352 used
TopTransactionContext: 73728 total in 4 blocks; 72608 free (60 chunks); 1120 used
TupleSort main: 5863645240 total in 686 blocks; 19112 free (22 chunks); 5863626128 used
Caller tuples: 234881024 total in 38 blocks; 2781352 free (12 chunks); 232099672 used
...
Grand total: 6100869152 bytes in 832 blocks; 3906248 free (745 chunks); 6096962904 used
여기서 주목할 점은 TupleSort main 컨텍스트입니다. 이 컨텍스트가 약 5.86GB(5,863,626,128 bytes)를 사용하고 있음을 알 수 있습니다. 전체 메모리 사용량(Grand total)도 6GB를 초과합니다. 이는 인덱스 생성을 위한 정렬 작업이 maintenance_work_mem으로 할당된 8GB에 근접하거나 초과하는 메모리를 요구했음을 명확히 보여줍니다.
오류 로그 상세 분석
PostgreSQL 서버 로그에서도 동일한 오류 메시지가 반복적으로 나타납니다.
2023-11-04 16:15:20.576 CST [4103] 오류: 메모리 용량 부족
2023-11-04 16:15:20.576 CST [4103] 상세 정보: 메모리 컨텍스트 "TupleSort main"에서 크기 9바이트 요청 실패.
2023-11-04 16:15:20.576 CST [4103] 문장: CREATE INDEX ACT_IDX_BYTEARRAY_NAME ON qs.ACT_GE_BYTEARRAY USING btree (NAME_);
2023-11-04 16:15:20.577 CST [4100] 오류: 메모리 용량 부족
2023-11-04 16:15:20.577 CST [4100] 상세 정보: 메모리 컨텍스트 "TupleSort main"에서 크기 9바이트 요청 실패.
2023-11-04 16:15:20.577 CST [4100] 컨텍스트: 병렬 작업자 프로세스
2023-11-04 16:15:20.577 CST [4100] 문장: CREATE INDEX ACT_IDX_BYTEARRAY_NAME ON qs.ACT_GE_BYTEARRAY USING btree (NAME_);
2023-11-04 16:15:20.577 CST [4102] 치명적 오류: 관리자 명령으로 인해 연결이 중단되었습니다.
2023-11-04 16:15:20.688 CST [4057] 로그: 백그라운드 작업자 "parallel worker" (PID 4103)이 종료되었습니다, 종료 코드 1
2023-11-04 16:15:20.692 CST [4057] 로그: 백그라운드 작업자 "parallel worker" (PID 4102)이 종료되었습니다, 종료 코드 1
로그는 여러 병렬 작업자(PID 4103, 4100)가 동시에 TupleSort main 컨텍스트에서 메모리 부족 오류를 겪었음을 보여줍니다. 이는 시스템의 maintenance_work_mem 설정이 전체 인덱스 정렬 작업을 감당하기에 충분하지 않다는 강력한 증거입니다. PostgreSQL은 병렬 인덱스 생성 시 maintenance_work_mem을 모든 병렬 작업자를 위한 공유 작업 영역으로 사용합니다. 따라서, 총 메모리 소비는 maintenance_work_mem을 초과하지 않아야 하지만, 정렬 대상 데이터의 복잡성이나 오버헤드로 인해 예상보다 많은 메모리가 필요할 수 있습니다.
근본 원인 및 해결 방안
6GB 크기의 테이블에 대해 인덱스를 생성할 때, 실제로 정렬 작업을 위해 필요한 임시 메모리는 단순히 테이블 크기를 넘어설 수 있습니다. 정렬 키(인덱스 컬럼의 값), 레코드 식별자, 정렬 알고리즘의 오버헤드 등이 포함되기 때문입니다. 8GB의 maintenance_work_mem이 할당되었음에도 5.8GB 이상을 사용하다가 메모리 요청 실패가 발생했다는 것은, 해당 작업에 최소 6GB 이상의 메모리가 필요했음을 의미합니다.
해결 방안:
-
maintenance_work_mem증설: 가장 직접적인 해결책은maintenance_work_mem값을 현재 8GB보다 더 크게 늘리는 것입니다. 예를 들어, 시스템 총 메모리가 31GB이고 여유가 있다면 12GB 또는 16GB까지 증설을 고려해볼 수 있습니다. 이 값을 조정할 때는 다른 PostgreSQL 설정(예:shared_buffers)과 시스템의 총 물리적 RAM을 고려하여 메모리 오버커밋이 발생하지 않도록 주의해야 합니다.ALTER SYSTEM SET maintenance_work_mem = '12GB'; -- 또는 ALTER SYSTEM SET maintenance_work_mem = '16GB'; SELECT pg_reload_conf(); -- 설정 적용을 위해 필요설정 변경 후 PostgreSQL을 재시작하거나
pg_reload_conf()를 호출하여 설정을 적용한 다음 다시 인덱스 생성을 시도합니다. -
병렬 인덱스 생성 비활성화 (선택 사항): 때로는 병렬 작업자 간의 메모리 할당 및 동기화 문제로 인해 이러한 오류가 발생할 수도 있습니다.
max_parallel_maintenance_workers를 0으로 설정하여 병렬 인덱스 생성을 일시적으로 비활성화한 후 인덱스를 생성하면 단일 프로세스로 작업이 진행되어 메모리 관리에 도움이 될 수 있습니다. 하지만 이 경우 인덱스 생성 시간이 더 오래 걸릴 수 있습니다.SET max_parallel_maintenance_workers = 0; CREATE INDEX ACT_IDX_BYTEARRAY_NAME ON qs.ACT_GE_BYTEARRAY USING btree (NAME_); SET max_parallel_maintenance_workers = DEFAULT; -- 작업 후 원래대로 복원 -
work_mem확인: 비록TupleSort main이 주로maintenance_work_mem의 영향을 받지만, 특정 서브 작업에서work_mem을 사용할 수도 있습니다. 만약maintenance_work_mem을 충분히 늘렸는데도 문제가 지속된다면,work_mem도 함께 상향 조정하는 것을 고려해볼 수 있습니다. 그러나 이 경우 각 개별 쿼리/세션에 영향을 주므로 신중해야 합니다. -
컬럼 데이터 타입 검토: 인덱스 대상 컬럼(여기서는 `NAME_`)의 데이터 타입이
bytea나text와 같이 가변 길이의 대용량 데이터를 저장하는 경우, 정렬 과정에서 많은 메모리가 필요할 수 있습니다. 가능한 경우 인덱스를 생성할 때 정렬 키의 크기를 최소화하는 방법을 고려합니다.
이 사례의 경우, maintenance_work_mem을 충분히 늘리는 것이 가장 효과적인 해결책입니다. 시스템의 여유 메모리를 고려하여 적절한 값을 찾아 적용하면 인덱스 생성 오류를 해결할 수 있습니다.