MongoDB 여러 메모리 지표 중 cursor를 발생하는 과정에서 spinlock 이 발생 시키지만 lock 으로는 잡히지 않는 내용을 보고 검색 중 tcmalloc 전체를 정리하는 시간을 가지게 되었습니다.
tcmalloc 이란 ?
- 구글에서 만든 메모리 할당하는 라이브러리(memory allocation (malloc))
- 메모리 풀을 사용하면
- 빠른 메모리 할당
- 메모리 단편화 감소
- 메모리 풀을 사용하면
- Thread Caching malloc (TCmalloc)
- 메모리를 Thread Local Cache와 Central Heap으로 나누어서 관리
- tcmalloc은 성능상의 이유로, 각각 스레드는 자체 로컬(Thread Local Cache) 여유 페이지 캐시와 중앙 여유 페이지(Central Heap) 캐시를 보유
- 메모리를 신청할 때 Thread Local 여유 페이지 캐시(32k 이하) 에서 사용 가능한 메모리를 찾고==> 사용 가능한 메모리가 없을 때만 Central Heap 페이지캐시(4k page)에서 추가 할당 적용 (Thread Local Cache는 32K이하의 작은 오브젝트 들을 담당하며 메모리가 부족할 시에는 Central Heap에서 메모리를 얻어와서 할당. 그리고 32K가 넘어가는 큰 오브젝트들은 Central Heap에다 4K의 페이지 단위로 나누어서 메모리 맵을 이용하여 할당)
- 단순 Central Heap은 일반적으로 사용하는 메모리 풀과 다를바가 없지만, Thread Local Cache가 있음으로 불필요한 동기화가 줄어들어 lock cost 가 꽤 많이 감소하여 성능향상 효과
- THread 의 수가 늘어날 수록 메모리 단위가 작을 수록 TCMalloc 이 효율이 더 뛰어남
- 출처: https://gamedevforever.com/31
MongoDB tcmalloc
- https://medium.com/daangn/memory-allocator-for-mongodb-1953f9cee06c (Memory Allocator for MongoDB - 당근마켓 팀블로그 Sunguck Lee 님)
- C++로 개발되어 메모리 할당과 해제를 직접 처리하는 C 언어와는 달리, C++에서는 (일반적으로) Heap Memory의 할당과 해제가 매우 많이 발생
- 별도의 Memory Allocator를 사용하지 않으면 리눅스 운영 체제의 기본 Memory Allocator인 PTMalloc2를 사용
- MongoDB에서도 PTMalloc2보다는 다른 더 나은 TCMalloc을 코드 수준에서 내장
- https://jira.mongodb.org/browse/SERVER-24268 (Investigate jemalloc as alternative to tcmalloc)
- mongos는 메모리 설정 파라메터가 없으며, 전통적으로 많은 사용자들이 mongos는 많은 메모리를 사용하지 않으며 일반적인 경우 100~200MB 정도로 할당해도 충분하다고 알고 있음
- MongoDB의 관리형 서비스인 Atlas MongoDB에서도 mongod(MongoDB 서버)와 mongos를 동일 인스턴스에 배포해서 서비스를 제공
- mongos를 통해서 아주 큰 데이터를 읽어오는 경우, mongos는 일시적으로 많은 데이터를 버퍼링해야 하며 순간적으로 메모리 사용량이 증가
- mongos는 적절한 페이징 사이즈만큼의 도큐먼트를 가져와서 클라이언트가 가져갈 때까지 버퍼링을 하기 때문에 1~2개의 클라이언트가 대량의 데이터를 읽어 간다고 해서 심각한 메모리 사용을 유발하지는 않음
- MongoDB에서는 메모리를 해제할 때 메모리도 캐시로 반환되고 tcmalloc Background에서 OS로 천천히 반환
- 기본적으로 tcmalloc은 최대 메모리(1GB, 1/8 * system_memory)까지 캐시하며, 이 값은 setParameter.tcmallocMaxTotalThreadCacheBytesParameter매개변수가 있지만 일반적으로 수정하지 않는 것이 좋음
- WiredTiger cacheSizeGB를 올바르게 구성(약 60%)
- Sort 의 경우 메모리 정렬에는 일반적으로 더 많은 Tempory Memory 가 필요
- 그렇기 때문에 index를 생성할 때 seak - sort - range 순서로 index 생성하는 이유
- primary 와 secondary 간의 replication의 간격이 너무 크면 안됨
- secondary 가 oplog를 저장하고 가져오기 위해(테일러커서) buffer (default maxsize 256mb) 유지해야하는데, 백그라운드는 buffer에서 oplog를 검색하고 계속 적용해야하기 때문에 secondary의 동기화가 느리면 버퍼가 최대 메모리로 계속 사용하게 됨
- colleaction 및 index의 수를 제어하여 메타 데이터의 메모리 오버헤드를 줄여야 함.
replSet:PRIMARY> db.serverStatus().tcmalloc
{
"generic" : {
"current_allocated_bytes" : NumberLong("55188352944"),
"heap_size" : NumberLong("102460903424")
},
"tcmalloc" : {
"pageheap_free_bytes" : NumberLong("11626672128"),
"pageheap_unmapped_bytes" : NumberLong("31339495424"),
"max_total_thread_cache_bytes" : NumberLong(1073741824),
"current_total_thread_cache_bytes" : 207674832,
"total_free_bytes" : NumberLong("4306382928"),
"central_cache_free_bytes" : NumberLong("4098687488"),
"transfer_cache_free_bytes" : 20608,
"thread_cache_free_bytes" : 207674832,
"aggressive_memory_decommit" : 0,
"pageheap_committed_bytes" : NumberLong("71121408000"),
"pageheap_scavenge_count" : 549140549,
"pageheap_commit_count" : 614667087,
"pageheap_total_commit_bytes" : NumberLong("119850820927488"),
"pageheap_decommit_count" : 560377066,
"pageheap_total_decommit_bytes" : NumberLong("119779699519488"),
"pageheap_reserve_count" : 7546,
"pageheap_total_reserve_bytes" : NumberLong("102460903424"),
"spinlock_total_delay_ns" : NumberLong("1232934807643"),
"release_rate" : 1,
"formattedString" : "------------------------------------------------\nMALLOC: 55188353520 (52631.7 MiB) Bytes in use by application\nMALLOC: + 11626672128 (11088.1 MiB) Bytes in page heap freelist\nMALLOC: + 4098687488 ( 3908.8 MiB) Bytes in central cache freelist\nMALLOC: + 20608 ( 0.0 MiB) Bytes in transfer cache freelist\nMALLOC: + 207674256 ( 198.1 MiB) Bytes in thread cache freelists\nMALLOC: + 498335744 ( 475.2 MiB) Bytes in malloc metadata\nMALLOC: ------------\nMALLOC: = 71619743744 (68301.9 MiB) Actual memory used (physical + swap)\nMALLOC: + 31339495424 (29887.7 MiB) Bytes released to OS (aka unmapped)\nMALLOC: ------------\nMALLOC: = 102959239168 (98189.6 MiB) Virtual address space used\nMALLOC:\nMALLOC: 5237759 Spans in use\nMALLOC: 182 Thread heaps in use\nMALLOC: 4096 Tcmalloc page size\n------------------------------------------------\nCall ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).\nBytes released to the OS take up virtual address space but no physical memory.\n"
}
}
replSet:SECONDARY> db.serverStatus().tcmalloc
{
"generic" : {
"current_allocated_bytes" : NumberLong("54850342264"),
"heap_size" : NumberLong("95095918592")
},
"tcmalloc" : {
"pageheap_free_bytes" : NumberLong("23660171264"),
"pageheap_unmapped_bytes" : NumberLong("12539207680"),
"max_total_thread_cache_bytes" : NumberLong(1073741824),
"current_total_thread_cache_bytes" : 247460608,
"total_free_bytes" : NumberLong("4046197384"),
"central_cache_free_bytes" : NumberLong("3798717832"),
"transfer_cache_free_bytes" : 18944,
"thread_cache_free_bytes" : 247460608,
"aggressive_memory_decommit" : 0,
"pageheap_committed_bytes" : NumberLong("82556710912"),
"pageheap_scavenge_count" : 20418440,
"pageheap_commit_count" : 41285079,
"pageheap_total_commit_bytes" : NumberLong("9607290798080"),
"pageheap_decommit_count" : 27551593,
"pageheap_total_decommit_bytes" : NumberLong("9524734087168"),
"pageheap_reserve_count" : 27728,
"pageheap_total_reserve_bytes" : NumberLong("95095918592"),
"spinlock_total_delay_ns" : NumberLong("395391394174"),
"release_rate" : 1,
"formattedString" : "------------------------------------------------\nMALLOC: 54850342840 (52309.4 MiB) Bytes in use by application\nMALLOC: + 23660171264 (22564.1 MiB) Bytes in page heap freelist\nMALLOC: + 3798717832 ( 3622.7 MiB) Bytes in central cache freelist\nMALLOC: + 18944 ( 0.0 MiB) Bytes in transfer cache freelist\nMALLOC: + 247460032 ( 236.0 MiB) Bytes in thread cache freelists\nMALLOC: + 468451328 ( 446.8 MiB) Bytes in malloc metadata\nMALLOC: ------------\nMALLOC: = 83025162240 (79179.0 MiB) Actual memory used (physical + swap)\nMALLOC: + 12539207680 (11958.3 MiB) Bytes released to OS (aka unmapped)\nMALLOC: ------------\nMALLOC: = 95564369920 (91137.3 MiB) Virtual address space used\nMALLOC:\nMALLOC: 5004341 Spans in use\nMALLOC: 181 Thread heaps in use\nMALLOC: 4096 Tcmalloc page size\n------------------------------------------------\nCall ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).\nBytes released to the OS take up virtual address space but no physical memory.\n"
}
}
지표들 내역
- pageheap_free_bytes : 페이지 힙에 매핑된 사용 가능한 페이지 byte
- 해당 영역은 요청 시 할당하여 사용 가능한 페이지
- OS에 의해 swap이 발생하지 않는 상태라면, 항상 해당 가상 메모리 사용량으로 계산
- total_free_bytes : central_cache_free_bytes + transfer_cache_free_bytes + thread_cache_free_bytes 로 구성
- pageheap_free_bytes 는 포함되어 있지 않음.
- https://github.com/mongodb/mongo/blob/master/src/mongo/util/tcmalloc_server_status_section.cpp 에서 아래 코드 참고
- sub.appendNumber("total_free_bytes", static_cast<long long>(central + transfer + thread));
- tcmalloc cache 사이즈를 확인하려면 pageheap_free_bytes 와 total_free_bytes 를 참조하면 가능
- central_cache_free_bytes : 클래스 size에 할당된 중앙 캐시 내의 free 바이트 수
- 항상 가상 메모리 샤용량으로 계산되며, os에서 기본 메모리를 swap 하여 사용하지 않는 한 물리적 메모리 사용량으로도 계산
- transfer_cache_free_bytes : 중앙 cache와 Thread Cache 간에 변환되기를 기다리는 free byte 수
- 항상 가상 메모리 샤용량으로 계산되며, os에서 기본 메모리를 swap 하여 사용하지 않는 한 물리적 메모리 사용량으로도 계산
- thread_cache_free_bytes : Thread Cache 내의 free byte 수
- 항상 가상 메모리 샤용량으로 계산되며, os에서 기본 메모리를 swap 하여 사용하지 않는 한 물리적 메모리 사용량으로도 계산
MongoDB 에서 spinlock
- collection 에 대한 작업을 하려고 할 때 해당 collection document에 lock 이 걸려 있으면, 지속적으로 앞에 있는 lock 이 만료되었는지 확인하는 작업(spin)
- spinlock 값이 증가한다면, lock 이 길어지고 있다는 것을 의미하며, slow query 등을 확인하여 오랫동안 동작하는 쿼리 등을 확인
- getmore 쿼리는 mongodb의 cursor 발생 하므로, spinlock 발생 시키지만, 이거는 DB log에서는 lock 으로 안 잡힘
spin lock 개념
- Thread가 단순히 loop(spin) 돌면서 Lock을 소유하고 있는 Thread가 lock 반환될 때까지 계속 확인하며 기다리는 상태
- Context Switching 으로 부하를 주기 보다는 잠시 기다리자는 컨셉으로 스위칭을 하지 않고 잠시 루프를 돌면서 재시도를 진행
그외 tcmalloc 관련 이슈
상황
- https://programmer.group/analysis-of-mongodb-tcmalloc-memory-cache.html
- 주로 Primary 만 사용하는 환경에서 Secondary 와 메모리 모니터링 중 Secondary 보다 11gb 더 많은 메모리 사용하는 것을 확인
- 동시 요청이 많을 경우 mongod에서 사용하는 메모리가 증가하다가 요청이 떨어진 후 천천히 해제되는 것을 관찰할 수 있는데, 이는 주로 tcmalloc 메모리 관리 전략에 기인
- https://jira.mongodb.org/browse/SERVER-37541
해결방안
- tcmallocRelease:1 를 실행하여 (release free) pageheap을 재활용
- db.adminCommand({tcmallocRelease: 1})
- 이 때 pageheap 전체를 lock 하기 때문에 온라인에서 사용할때는 주의 (사용량이 적을 때 사용)
- 하지만 해당 명령어는 가급적 사용하지 않는 것이 좋음(문제라고 판단되기 전까지..)
- 실제로는 메모리를 낭비하지 않고 해당 메모리를 이용하여 다른 곳에서도 사용할 수 있기 때문
https://developer.aliyun.com/article/685044
https://jira.mongodb.org/browse/SERVER-37541
https://jira.mongodb.org/browse/SERVER-33296
- current_allocated_byutes 가 8gb
- 하지만 heap_size는 현재 14gb 에 이르고 있음 (swap 사용한 내용 포함 / mem-resident와 비슷)
- pageheap_free_bytes의 누적으로 인한 것으로 예상
- TCMALLOC_AGGRESSIVE_DECOMMIT 를 이용하여 해결(설정하게 되면 tcmalloc이 여유 페이지를 os로 적극적으로 반환하도록 하는 역할)
- 하지만 부정적인 성능 영향이 있을것 같아 원인 해결이 필요해 보임
- mongodb에서는 tcmalloc 이 해당 부정적인 영향보다 더 큰 이점(성능)이 있다고 하여 우선순위에서 밀려 미해결중
- Mongo 4.4.10 에서도 발생하고 있다 함.
- https://jira.mongodb.org/browse/SERVER-31417
반응형
'MongoDB' 카테고리의 다른 글
[MongoDB] Logical Session / Causal Consistency (0) | 2022.12.08 |
---|---|
[MongoDB] taskExecutorPoolSize (0) | 2022.08.08 |
[MongoDB] Replica Set to StandAlone (v4.2) (0) | 2022.07.04 |
[MongoDB] Upgrade 이슈 (4.0 to 4.2) - MinValidDocument.oplogDeleteFromPoint is an unknown field (0) | 2022.07.02 |
[MongoDB] 3.4 to 4.2 upgrade (rpm) (0) | 2022.07.02 |