Klaytn State Trie Cache Series #1: Cache 문제 원인 파악하기

Tech at Klaytn
Klaytn
Published in
7 min readNov 13, 2020

전체 포스팅 목록은 여기에서 확인하세요.

Klaytn은 블록체인 플랫폼의 성능을 개선하기 위해 다양한 노력들을 했습니다. 아래의 포스팅을 통해 state trie cache 성능 개선 과정을 살펴보고자 합니다.

  1. Cache 문제 원인 파악하기
  2. 최적의 cache 찾기
  3. State trie cache miss 계산하기
  4. Cache Size Tuning 하기

이 포스팅에서는 Klaytn에 관련한 실험을 진행하다가 발견한 문제와 문제의 원인이었던 Go 언어의 GC(Garbage Collector)에 대해 살펴보겠습니다. 다음은 Klaytn 테스트 도중 발생한 현상입니다.

Prometheus에서 제공하는 API로 메모리 사용량 조회

Klaytn binary에서 3500 TPS로 transaction을 처리하였을때 약 100G의 메모리를 사용합니다. 어디서 많이 사용하고 있는지 확인하기 위해 Go언어에서 제공하는 메모리 프로파일링을 이용하여 그 결과를 확인하였습니다.

▶ go tool pprof cn-mem0.prof
File: kcn
Build ID: 7b45b11c163a99518095ffb64083e4aa61fd321f
Type: inuse_space
Time: Mar 26, 2020 at 8:56am (KST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 41.91GB, 96.33% of 43.50GB total
Dropped 382 nodes (cum <= 0.22GB)
Showing top 10 nodes out of 77
flat flat% sum% cum cum%
30GB 68.97% 68.97% 30GB 68.97% github.com/allegro/bigcache/queue.NewBytesQueue
5.65GB 12.98% 81.95% 5.65GB 12.99% github.com/allegro/bigcache.(*cacheShard).set
1.53GB 3.52% 85.47% 1.53GB 3.52% reflect.New
1.25GB 2.87% 88.35% 2.60GB 5.97% github.com/klaytn/klaytn/ser/rlp.decodeBigInt

메모리 프로파일링을 통해 어디서 몇 바이트의 메모리를 사용하고 있는지 확인할 수 있습니다. 위 결과 중 Showing nodes accounting for 41.91GB, 96.33% of 43.50GB total를 통해 kcn binary에서 총 43.5GB를 사용하고 있고, 그 중 96.33%인 41.91GB가 어디서 사용되고 있는지 확인할 수 있습니다. 또한, 30GB 68.97% github.com/allegro/bigcache/queue.NewBytesQueue를 통해 30GB(68.97%)는 bigcache에서 사용하고 있는 것을 확인할 수 있습니다.

두 결과에서 이상한 점을 확인할 수 있습니다. Prometheus에서 제공하는 메모리 사용량 library에 의하면 약 100GB를 kcn에서 사용하고 있고, 메모리 프로파일링 결과(43.50GB total)에서는 kcn binary에서 총 43.5GB를 사용하고 있다는 결과를 확인할 수 있습니다. 56.5GB(=100GB - 43.5GB)의 메모리가 어디서 사용하고 있는지 추적할 수 없습니다.

메모리 누락의 원인 중 의심되는 것 중 하나는 메모리를 가장 많이 사용하고 있는 Bigcache였습니다. Bigcache가 메모리를 더 많이 사용하고 있는지 확인하기 위해 같은 환경의 서버 2대에 cache size만 30GB, 0GB로 할당하여 테스트를 진행하였습니다. 다음은 두 서버의 top과 메모리 프로파일링 결과입니다.

(Top명령의 결과는 GiB, Prometheus에서 제공하는 library의 결과는 GB이며 두 값은 똑같은 값을 나타냅니다.)

Cypress sync test
AWS Instance : m5.8xlarge
memory size: 128G
cache size: 30G, 0G

top 명령어 결과 (좌: cache 30G, 우: cache 0GB)
Go Memory Profiling 결과 (좌: cache 30G, 우: cache 0GB)

Bigcache를 할당해 준 서버에서는 Top과 메모리 프로파일링에서 총 메모리 사용량이 각각 70GB, 35GB로 35GB의 메모리 누락이 있는것을 확인할 수 있습니다. 반면에 Bigcache를 사용하고 있지 않는 서버에서는 Top과 메모리 프로파일링에서 총 메모리 사용량이 각각 5GB, 2GB로 메모리 누락이 3GB 있는 것을 확인할 수 있습니다.

위 실험을 통해, Bigcache를 사용하면 할당해 준 메모리보다 더 많은 메모리를 사용할 수 있다는 것을 유추해 볼 수 있습니다. 또한 Bigcache를 사용하지 않아도 3GB정도의 메모리 누락이 발생하는 것을 확인할 수 있었습니다. GC의 동작 때문에, 어떤 Go 프로그램이 실행되더라도 메모리 프로파일링에서의 사용량과 실제 사용량은 차이가 생길 수 있습니다.

또한, 오랫동안 큰 heap 메모리를 잡고 있고, 이를 할당할 때 pointer를 사용하고 있으면 굉장히 많은 메모리가 사용된다는 것을 이 글을 통해 알 수 있었습니다.

GC(Garbage Collector)는 프로그램이 동적으로 할당했던 메모리 중 더 이상 사용하고 있지 않는 영역을 찾고, 이를 할당 해제하여 다른 곳에서 메모리를 사용할 수 있도록 합니다. 이를 위해 Go언어의 GC(Garbage Collector)는 사용하고 있지 않은 공간이 할당되어 있는지 확인합니다. 이 때 확인하는 대상은 바로 포인터 입니다. 포인터가 많거나 큰 메모리를 사용하고 있다면, GC에서 이를 탐색하는데 많은 메모리를 사용합니다.

따라서, GC가 실행을 시작하기 전에는 메모리 사용량이 44GB이었다가 GC가 동작하면서 메모리 사용량이 100GB만큼 추가적으로 사용되는 것입니다. 또한, 메모리 프로파일링 시점은 GC가 실행을 마친 후이기 때문에, 기존 메모리 사용량인 44GB만 확인할 수 있었던 것입니다. Klaytn과 같이 큰 Bigcache를 할당해 주는 경우에는 GC에서 항상 더 많은 메모리를 사용하고 있었던 것입니다.

이러한 추가적인 메모리 사용은 의도치 않은 out of memory 에러를 발생시킬 수 있습니다. Klaytn 처럼 오랜기간 동작해야 하는 프로그램의 경우, 의도치 않은 많은 메모리를 사용하여 프로세스가 종료되는 경우가 발생하지 않아야 합니다. 이 메모리 누락 문제를 어떻게 해결하였는지 다음 포스트에서 알아보겠습니다.

--

--