review - 대용량 아키텍처와 성능 튜닝

dvmflstm·2020년 7월 26일
0

대용량 아키텍처와 성능 튜닝

아키텍처 설계와 관련된 내용들을 폭넓은 범위에 걸쳐 다루고 있는 책이다. 특히 기술적인 내용 이외에 비즈니스 아키텍쳐를 설계하는 방법에 대해 설명하는 부분도 존재한다. 기대했던 내용은 아니지만 유익한 내용이었던 것 같다. 다만 책이 쓰여진 지 시간이 좀 지났기 때문에, 최신 레퍼런스 아키텍쳐를 설명하는 부분에서는 람다 아키텍쳐를 제외하고는 크게 새롭게 배울 만한 점이 없었던 것 같다. 트렌드와는 상관없이 JVM을 좀 더 깊게 다루고 성능 튜닝을 하는 방법에 대해 소개하는 부분은 굉장히 유익한 내용이라고 생각한다.

NoSQL

nosql이라는 개념을 접한 지는 꽤 오래되었고, 이전에 다니던 회사에서도 메인 데이터베이스로 nosql을 사용했었지만, 잘 사용하는 방법에 대해 제대로 학습한 적은 없었던 것 같다. 여러 사이드 프로젝트에서 nosql을 쓰려고 노력하면서도 어떤 부분에서 장점이 있는 건지 확실히 실감하지 못했는데, 본 책에서 소개하는 nosql 관련 내용을 읽으면서 어느정도 생각을 정리할 수 있었던 것 같다.

NoSQL의 등장 배경

기존의 컴퓨팅 시스템은 기업의 업무를 자동화하고 효율화하는 데 그 목적이 있었다. 그래서 기업의 복잡한 데이터를 저장하고 그 데이터 간의 관계를 정의하고 분석하는 데 최적화되어 있는 RDBMS가 메인 데이터베이스로써 지배적으로 사용되어 왔었다.

하지만 2000년대에 들어서면서 인터넷의 발전과 함께 대형 B2C 서비스들이 등장하기 시작했다. 이러한 서비스들은 데이터 간의 복잡한 관계는 필요 없으며, 이전보다 훨씬 큰 저장 용량과 높은 성능을 필요로 했다. 그에 따라 이러한 니즈에 최적화된 NoSQL이 각광받기 시작했다.

NoSQL의 특징

  • 데이터 간의 관계를 저장하지 않음
  • RDBMS에 비해 훨씬 대용량의 데이터 저장 가능
  • 분산형 구조
  • 고정되지 않은 테이블 스키마

NoSQL 데이터 모델링

개체 모델 지향에서 쿼리 결과 지향 모델링

RDBMS의 모델링 기법은 저장하고자 하는 도메인 모델을 먼저 분석한 후에 개체 간의 관계를 식별하고 테이블을 추출해내고 테이블을 이용하여 쿼리를 구현하여 결과를 뽑아내는 방식이다. 반면 Nosql의 경우는 RDBMS와는 역순으로 모델링을 진행해야 한다. 어떤 쿼리 결과가 필요한지를 정의한 후에, 이 쿼리 결과를 쉽게 얻어낼 수 있도록 데이터 저장 구조를 설계해야한다.

정규화에서 역정규화로

RDBMS에서는 데이터의 일관성과 도메인 모델과의 일치성을 위해 데이터 모델을 정규화한다. 반대로 nosql은 쿼리의 효율을 위해서 되도록이면 데이터를 중복해서 저장하는 방법인 역정규화가 권장된다.

Nosql 데이터 모델링 절차

1. 도메인 모델 파악

Nosql이라고 해서 도메인 모델을 파악하고 개체들 간의 관계를 분석하는 작업을 건너뛰어도 되는 것은 아니다. 어떠한 데이터베이스를 쓰든 저장할 데이터에 대한 명확한 이해는 필수다. 책에서는 ERD 까지 그리고 있지만, 개인적으로는 대부분의 경우 그 정도까지는 필요 없이 UML 다이어그램 정도의 수준으로 모델링을 하는 것으로 충분할 것 같다는 생각이 든다.

2. 쿼리 결과 디자인

클라이언트의 요구사항을 파악하고, 각각의 쿼리에 대해 어떤 결과를 원하는 지를 정리하는 단계이다. Nosql의 역정규화는 이 쿼리 결과를 만족시키기 위해 일어나는 과정이라고 생각해도 좋을 것 같다. 최대한 단순하게 각각의 쿼리 결과를 만족시키기 위해 주로 composite key를 많이 사용하며, 이것을 사용하는 과정에서 역정규화는 자연스럽게 일어난다.

JVM과 톰캣 튜닝

GC (Garbage Collection)

GC는 더 이상 사용하지 않는 객체에 대한 참조를 해제하는 방법으로, C와는 대조되는 java 언어의 대표적인 특징이다. 동적으로 할당한 객체에 대한 메모리를 명시적으로 해제할 필요가 없기 때문에 코드를 작성하기 편하다는 장점이 있지만, 모든 스레드를 중지시키는 full GC 작업이 원하지 않는 순간에 일어나게 되면 어플리케이션 성능에 큰 문제가 생길 수도 있다. 그래서 GC 작업에 대한 로그를 수집하고 분석하는 것은 어플리케이션의 성능을 튜닝하는데 매우 중요하다.

GC 로그 수집과 분석 방법

java 커맨드를 실행할 때 -verbosegc 옵션을 주면 GC 작업에 대한 로그를 표준 출력으로 뽑아볼 수 있다. 추가로 JDK 1.4 이상 버전에서는 -XX:+PrintGCDetails 옵션을 통해 좀 더 자세한 GC 정보를 수집할 수 있다.

[GC 40549K->20909K(64768K), 0.0484179 secs]
[Full GC 43960K->1749K(64768K), 0.1452695 secs]
.
.

GC 관련 파라미터

-X 옵션과 -XX 옵션은 표준 옵션이 아니라 벤더별 JVM에서 따로 제공하는 옵션이므로 예고 없이 변경되거나 없어질 수 있음에 주의해야 한다.

전체 힙 크기 조정 옵션

-ms와 -mx로 최소 및 최대 힙 사이즈를 지정할 수 있다. 예를 들어, -ms512m -mx 1024m으로 설정하면 JVM은 전체 힙 사이즈를 상황에 따라 이 범위 내에서 조절하게 된다.

Perm 크기 조정 옵션

-XX:MaxPermSize와 같은 방식으로 지정할 수 있다. Perm size에 따라 자바 어플리케이션은 구동 시에 OutOfMemoryError를 뱉을 수 있는데, 이런 상황에서 MaxPermSize를 적절하게 설정해줌으로써 문제를 해결할 수 있다.

java 8 부터는 perm generation 영역이 meta space 영역으로 대체됨에 따라 PermSize 관련 옵션들도 deprecated 되었다고 한다.

New 영역과 Old 영역의 조정

-XX:NewRatio=2 와 같은 방식으로 지정할 수 있다. ratio는 (old 영역 크기 / new 영역 크기)이다. jdk 1.4에서는 -XX:NewSize 옵션을 통해 사이즈를 직접 지정할 수도 있다.

-server와 -client 옵션

해당 어플리케이션이 서버용으로 쓰일 건지, 클라이언트 용으로 쓰일 건지에 따라 최적화된 환경을 제공해준다. 서버 애플리케이션의 경우 객체들이 해제되지 않고 장시간 지속되는 경우가 드물기 때문에 New 영역이 Old 영역에 비해 크게 배정되고, 클라이언트 애플리케이션의 경우 GUI 컴포넌트와 같이 애플리케이션이 종료될 때 까지 남아있는 개체의 비중이 높아서 상대적으로 Old 영역의 비중이 높다.

이 옵션은 가장 간단한 옵션이지만 JVM 최적화에 아주 큰 부분을 차지하기 때문에 반드시 적절히 사용하는 것을 권한다.

JVM GC 튜닝

앞서 배운 옵션들을 바탕으로 상황에 맞게 GC 작업을 튜닝하는 방법에 대해 알아본다.

1. 애플리케이션의 종류와 튜닝 목표 값 결정

JVM 튜닝의 목표를 설정하는 것이 가장 처음 해야할 일이다. 목표가 메모리를 적게 쓰는 것인지, GC 횟수를 줄이는 것인지, GC에 걸리는 시간을 줄이는 것이 목표인지 등을 먼저 정하고 나서 그 목표에 맞게 JVM 파라미터를 조정해야 한다.

2. 힙 크기와 Perm 사이즈 설정

우선 대략적으로 힙 크기와 Perm 사이즈를 설정한다. 이후의 과정을 통해 보다 구체적으로 메모리 사이즈를 설정할 수 있다.

3. 테스트와 로그 분석

GC 로그를 수집하기 위한 -verbosegc 옵션 및 -XX:+PrintGCDetail 옵션을 설정한다. 그리고 nGrinder 등의 부하 테스트 도구를 이용해 애플리케이션에 부하를 줘서 GC 로그를 수집한다. 스트레스 테스트의 시나리오 및 도구 선정은 튜닝의 효과를 확인하기 위해 매우 중요한 요인이다.

4. GC 수행 시간 분석

기본적으로, full GC가 일어나는 횟수를 줄이기 위해서는 Old 영역의 크기를 늘려야 하고, full GC에 걸리는 시간을 줄이기 위해서는 Old 영역의 크기를 줄여야 한다. 1번 스텝에서 결정한 튜닝의 목표에 따라 적절한 튜닝이 필요하다. 만약 full GC가 발생하는 횟수도 줄이고, 걸리는 시간도 줄여야 한다면 애플리케이션 인스턴스의 갯수를 늘려 로드밸런싱하는 등의 전략이 필요할 것이다.

5. 파라미터 변경

테스트와 로그 분석을 반복하면서 파라미터를 적절하게 변경한다. Perm 사이즈는 로그에서 명확하게 나타나기 때문에 크게 조정할 필요가 없고, new 영역 내의 eden 영역과 survivor 영역의 크기는 거의 조정하지 않는다. 중요한 것은 new generation과 old generation 사이의 비율을 어떻게 조정하는가이다.

동시에 어떤 부분에 향상 포인트를 두냐에 따라 적절한 GC 알고리즘을 선정하는 것도 중요하다.

  • Performance 중시 -> Parallel GC
  • Responsiveness 중시 -> Concurrent GC, Incremental GC
  • 일반 -> Default GC
profile
서울대학교 컴퓨터공학부 github.com/BaekGeunYoung

0개의 댓글