Java 성능을 크게 향상시키는 방법은 무엇입니까?


23

LMAX 팀은 1ms 미만의 대기 시간으로 100k TPS 를 수행 할 수있는 방법에 대한 프레젠테이션을 진행했습니다 . 그들은 프레젠테이션을 블로그 , 기술 문서 (PDF)소스 코드 자체로 백업했습니다 .

최근 Martin Fowler 는 LMAX 아키텍처에 대한 훌륭한 논문을 발표했으며 현재 초당 6 백만 건의 주문을 처리 할 수 ​​있으며 팀이 다른 수준의 성능 향상을 위해 취한 몇 가지 단계를 강조합니다.

지금까지 Business Logic Processor 속도의 핵심은 모든 것을 메모리 내에서 순차적으로 수행하는 것이라고 설명했습니다. 이 작업을 수행하는 것만으로도 개발자는 10K TPS를 처리 할 수있는 코드를 작성할 수 있습니다.

그런 다음 좋은 코드의 간단한 요소에 집중하면이를 100K TPS 범위로 끌어 올릴 수 있다는 것을 알게되었습니다. 이것은 잘 짜여진 코드와 작은 방법 만 필요합니다. 본질적으로 이것은 핫스팟이 더 나은 최적화 작업을 수행하고 CPU가 실행되는 코드를보다 효율적으로 캐싱 할 수있게합니다.

또 다른 수준으로 올라가려면 좀 더 영리했습니다. LMAX 팀이 도움이되는 것으로 몇 가지가 있습니다. 하나는 캐시 친화적이고 가비지에주의하도록 설계된 Java 콜렉션의 사용자 정의 구현을 작성하는 것이 었습니다.

최고 수준의 성능에 도달하는 또 다른 기술은 성능 테스트에 주목하는 것입니다. 나는 사람들이 성능을 향상시키는 기술에 대해 많이 이야기한다는 것을 오랫동안 알고 있지만 실제로 차이를 만드는 것은 테스트하는 것입니다.

파울러는 몇 가지 발견 된 사실이 있다고 언급했지만 몇 가지만 언급했다.

그러한 수준의 성능에 도달하는 데 도움이되는 다른 아키텍처, 라이브러리, 기술 또는 "사물"이 있습니까?


11
"어떤 다른 아키텍처, 라이브러리, 기술 또는"사물 "이 그러한 수준의 성능을 달성하는 데 도움이됩니까?" 왜 물어? 그 인용은 최종 목록입니다. 다른 많은 것들이 있지만 그중 어느 것도 그 목록의 항목에 영향을 미치지 않습니다. 다른 사람이 이름을 지정할 수있는 것은 그 목록만큼 도움이되지 않습니다. 지금까지 만들어진 최고의 최적화 목록 중 하나를 인용했을 때 나쁜 생각을하는 이유는 무엇입니까?
S.Lott

생성 된 코드가 시스템에서 어떻게 실행되는지 확인하기 위해 어떤 도구를 사용했는지 알아두면 좋을 것입니다.

1
나는 사람들이 모든 종류의 기술로 맹세한다고 들었습니다. 내가 가장 효과적인 것은 시스템 레벨 프로파일 링입니다. 프로그램 및 워크로드가 시스템을 실행하는 방식에 병목 현상을 표시 할 수 있습니다. 성능에 관한 잘 알려진 지침을 준수하고 나중에 쉽게 조정할 수 있도록 모듈 식 코드를 작성하는 것이 좋습니다 ... 시스템 프로파일 링에 문제가 있다고 생각하지 않습니다.
ritesh

답변:


21

고성능 트랜잭션 처리를위한 모든 종류의 기술이 있으며 Fowler의 기사에 나오는 기술은 최첨단 기술 중 하나 일뿐입니다. 다른 사람의 상황에 적용 할 수도 있고 적용되지 않을 수있는 많은 기술을 나열하는 것보다는 기본 원칙과 LMAX가 많은 기술을 다루는 방법에 대해 논의하는 것이 좋습니다.

대규모 트랜잭션 처리 시스템의 경우 가능한 한 다음을 모두 수행하려고합니다.

  1. 가장 느린 스토리지 계층에서 소요되는 시간을 최소화하십시오. 최신 서버에서 가장 빠르거나 느린 서버 : CPU / L1-> L2-> L3-> RAM-> 디스크 / LAN-> WAN. 가장 빠른 최신 자기 디스크에서 가장 느린 RAM으로의 점프는 순차적 액세스를 위해 1000x 이상입니다 . 랜덤 액세스는 더욱 악화됩니다.

  2. 대기 시간을 최소화하거나 제거하십시오 . 이는 가능한 한 적은 상태를 공유하고, 상태 공유 해야하는 경우 가능할 때마다 명시 적 잠금을 피하는 것을 의미합니다.

  3. 작업량을 분산 시키십시오. CPU는 훨씬 더 빨리 지난 몇 년 동안 못 했어,하지만 그들은 작은 입수, 8 개 코어 서버에 매우 일반적이다. 그 외에도 Google의 접근 방식 인 여러 머신에 작업을 분산시킬 수도 있습니다. 이것에 대한 좋은 점은 I / O를 포함한 모든 것을 확장한다는 것입니다 .

Fowler에 따르면 LMAX는 다음과 같은 방법을 사용합니다.

  1. 계속 모두 에서 메모리의 상태를 모든 시간. 전체 데이터베이스가 메모리에 들어갈 수 있다면 대부분의 데이터베이스 엔진은 실제로 어쨌든이 작업 을 수행하지만 실시간 거래 플랫폼에서 이해할 수있는 기회를 남기고 싶지 않습니다. 엄청난 위험을 초래하지 않고이 문제를 해결하기 위해 경량의 백업 및 장애 조치 인프라를 구축해야했습니다.

  2. 입력 이벤트 스트림에 잠금없는 큐 ( "중단기")를 사용하십시오. 기존의 내구성에 대비 메시지 큐 결정적입니다 하지 무료 잠금, 실제로 보통 고통스럽게 느린 포함 분산 트랜잭션을 .

  3. 별로. LMAX는 워크로드가 상호 의존적이라는 사실에 기초하여 버스 아래에 이것을 던집니다. 하나의 결과는 다른 것의 매개 변수를 변경합니다. 이것은 중요한 경고이며 Fowler가 명시 적으로 지적합니다. 그들은 어떻게해야합니까 몇 가지 장애 복구 기능을 제공하기 위해 동시성을 사용하지만, 모든 비즈니스 로직은에서 처리 단일 스레드 .

LMAX가 대규모 OLTP에 대한 유일한 접근 방식 은 아닙니다 . 또한 자체적으로는 훌륭하지만 성능 수준을 높이기 위해 최첨단 기술을 사용할 필요 는 없습니다 .

위의 모든 원칙 중 # 3은 아마도 가장 중요하고 가장 효과적 일 것입니다. 솔직히 하드웨어는 저렴하기 때문입니다. 수십 개의 코어와 수십 개의 시스템에서 워크로드를 올바르게 분할 할 수 있다면 기존 병렬 컴퓨팅 기술 의 한계가 하늘에 있습니다. 많은 메시지 대기열과 라운드 로빈 배포자만으로도 처리량을 얼마나 줄일 수 있는지 놀랄 것입니다. LMAX만큼 효율적이지는 않지만 실제로는 근접하지는 않지만 처리량, 대기 시간 및 비용 효율성은 별도의 문제이며 여기서는 처리량에 대해 구체적으로 이야기하고 있습니다.

LMAX와 같은 특별한 요구 사항, 특히 성급한 디자인 선택이 아닌 비즈니스 현실에 해당하는 공유 상태가 있다면 그 구성 요소를 사용해 보는 것이 좋습니다. 그렇지 않으면 해당 요구 사항에 적합합니다. 그러나 우리가 단순히 높은 확장성에 대해 이야기하고 있다면 분산 시스템에 대한 더 많은 연구를 할 것을 권장합니다. 왜냐하면 오늘날 대부분의 조직에서 사용하는 표준 접근 방식이기 때문입니다 (Hadoop 및 관련 프로젝트, ESB 및 관련 아키텍처, CQRS 언급 등).

SSD는 또한 게임 체인저가 될 것입니다. 틀림없이, 그들은 이미 있습니다. 이제 RAM에 대한 액세스 시간이 비슷한 영구 스토리지를 보유 할 수 있으며 서버급 SSD는 여전히 비싸지 만 채택률이 높아지면 결국 가격이 내려갑니다. 그것은 광범위하게 연구되어 왔으며 결과는 꽤 놀랍고 시간이 지남에 따라 더 좋아질 것이므로 전체 "메모리에 모든 것을 유지"개념은 이전보다 훨씬 덜 중요합니다. 다시 한 번, 가능할 때마다 동시성에 초점을 맞추려고합니다.


파울러의 논문이 망각 알고리즘을 캐싱하기위한 각주에 대한 언급이 없다면 en.wikipedia.org/wiki/Cache-oblivious_algorithm (잘 들어 맞음) 카테고리 번호 1 이상) 나는 결코 그들에게 걸려 넘어지지 않았을 것입니다. 그렇다면 ... 위의 각 범주와 관련하여 사람이 알아야 할 3 가지 주요 사항을 알고 있습니까?
다코타 노스

@Dakotah : 나는조차하지 않을 시작 내가 완전히 디스크를 제거 할 때까지하지 않는 한 캐시 지역에 대한 걱정에 I / 시간의 대부분은 대부분의 응용 프로그램에서 대기 소요되는 곳이다 O. 그 외에도 "사람이 알아야 할 3 가지"는 무엇을 의미합니까? 무엇을 알기 위해 무엇입니까?
Aaronaught

RAM 액세스 대기 시간 (~ 10 ^ -9s)에서 자기 디스크 대기 시간 (평균 10 ~ 3 초)으로의 급증은 1000 배보다 큰 몇 차수입니다. SSD조차도 여전히 수백 마이크로 초 단위로 액세스 시간이 측정됩니다.
Sedate Alien

@Sedate : 대기 시간은 그렇습니다. 그러나 이것은 원시 대기 시간보다 처리량에 대한 문제이며, 액세스 시간이 지나고 전체 전송 속도에 도달하면 디스크는 그리 나쁘지 않습니다. 그렇기 때문에 랜덤 액세스와 순차적 액세스를 구분했습니다. 랜덤 액세스 시나리오는 않습니다 주로 대기 시간이 문제가된다.
Aaronaught

@Aaronaught : 다시 읽을 때, 당신이 맞다고 생각합니다. 아마도 모든 데이터 액세스는 가능한 한 순차적이어야합니다. RAM에서 순서대로 데이터에 액세스 할 때 상당한 이점을 얻을 수 있습니다.
Sedate Alien

10

이것에서 배울 수있는 가장 큰 교훈은 기본부터 시작해야한다는 것입니다.

  • 좋은 알고리즘, 적절한 데이터 구조 및 "정말 어리석은"행동을하지 않는 것
  • 잘 구성된 코드
  • 성능 시험

성능 테스트 중에 코드를 프로파일 링하고 병목 현상을 찾아 하나씩 수정합니다.

너무 많은 사람들이 "하나씩 수정"부분으로 바로 이동합니다. "자바 컬렉션의 사용자 정의 구현"을 작성하는 데 많은 시간을 할애합니다. 시스템이 느리게 된 이유는 캐시 누락 때문이기 때문입니다. 이는 기여 요인이 될 수 있지만 저수준 코드를 바로 조정하면 LinkedList를 사용해야 할 때 ArrayList를 사용하는 큰 문제가 발생하거나 시스템의 실제 이유가 누락 될 수 있습니다 ORM이 엔티티의 하위 항목을 지연로드하여 모든 요청에 ​​대해 데이터베이스로 400 개의 개별 트립을 수행하기 때문에 느립니다.


7

LMAX 코드에 대해서는 특별히 언급하지 않았지만 충분히 설명되어 있다고 생각하기는하지만 성능을 크게 향상시킬 수있는 몇 가지 예는 다음과 같습니다.

항상 그렇듯이 이러한 기술 은 문제가 있고 성능을 개선해야한다는 것을 알고 나면 적용해야합니다. 그렇지 않으면 조기 최적화를 수행하는 것일 수 있습니다.

  • 올바른 데이터 구조를 사용하고 필요한 경우 사용자 정의 구조를 만드십시오. 올바른 데이터 구조 설계는 미세 최적화에서 얻을 수있는 개선을 방해하므로 먼저 수행하십시오. 알고리즘이 많은 빠른 O (1) 임의 액세스 읽기에 대한 성능에 의존하는 경우이를 지원하는 데이터 구조가 있는지 확인하십시오! 매우 정확한 O (1) 인덱싱 된 읽기를 활용하기 위해 배열에서 데이터를 표현할 수있는 방법을 찾는 등의 방법으로이 문제를 해결하는 것이 좋습니다.
  • CPU가 메모리 액세스보다 빠릅니다 . 메모리가 L1 / L2 캐시에없는 경우 임의의 메모리를 한 번 읽는 데 걸리는 시간에 많은 계산을 수행 할 수 있습니다. 일반적으로 메모리 읽기를 절약하는 경우 계산을 수행하는 것이 좋습니다.
  • final -make 필드, 메소드 및 클래스 final을 사용하여 JIT 컴파일러를 지원하면 실제로 JIT 컴파일러에 도움이되는 특정 최적화가 가능합니다. 구체적인 예 :

    • 컴파일러는 최종 클래스에 서브 클래스가 없다고 가정 할 수 있으므로 가상 메소드 호출을 정적 메소드 호출로 전환 할 수 있습니다.
    • 컴파일러는 정적 최종 필드를 성능 향상을 위해 상수로 취급 할 수 있습니다. 특히 상수가 컴파일 타임에 계산할 수있는 계산에 사용되는 경우입니다.
    • Java 오브젝트를 포함하는 필드가 final로 초기화되면 옵티마이 저는 널 점검 및 가상 메소드 디스패치를 ​​모두 제거 할 수 있습니다. 좋은.
  • 컬렉션 클래스를 배열로 교체 -코드를 읽기 어렵고 유지하기가 쉽지만 간접 계층을 제거하고 많은 배열 액세스 최적화의 이점을 얻음으로써 거의 항상 빠릅니다. 일반적으로 내부 루프 / 성능에 민감한 코드는 병목 현상으로 식별 된 후 좋은 아이디어이지만 가독성을 위해 달리 피하십시오!

  • 가능한 경우 기본 요소를 사용하십시오. 기본 요소는 기본적으로 오브젝트 기반의 동등 물보다 빠릅니다. 특히, 권투는 엄청난 양의 오버 헤드를 추가하고 불쾌한 GC 일시 중지를 유발할 수 있습니다. 성능 / 대기 시간이 걱정되는 경우 기본 요소를 상자에 넣지 마십시오.

  • 낮은 수준의 잠금 최소화 -잠금은 낮은 수준에서 매우 비쌉니다. 전체 잠금을 피하거나 대략적인 수준에서 잠금을 설정하여 큰 데이터 블록에 대해 가끔씩 만 잠금을 설정하면 잠금 또는 동시성 문제에 대해 전혀 걱정할 필요없이 낮은 수준의 코드가 진행될 수 있습니다.

  • 메모리 할당을 피하십시오. JVM 가비지 콜렉션은 매우 효율적이므로 실제로 전체 속도가 느려질 수 있지만 대기 시간이 매우 짧고 GC 일시 중지를 최소화해야하는 경우 매우 유용합니다. 할당을 피하기 위해 사용할 수있는 특수한 데이터 구조가 있습니다 . 특히 http://javolution.org/ 라이브러리는 우수하고 주목할만한 것입니다.

나는 방법을 final만드는 것에 동의하지 않는다 . JIT는 메소드가 대체되지 않음을 알 수 있습니다. 또한 하위 클래스가 나중에로드되면 최적화를 취소 할 수 있습니다. 또한 "메모리 할당을 피 하십시오 " 는 GC 작업을 어렵게하여 속도를 늦출 수 있으므로주의해서 사용하십시오.
maaartinus

@maaartinus : final일부 JIT 와 관련하여 다른 JIT가 알아낼 수 있습니다. 많은 성능 조정 팁과 마찬가지로 구현에 따라 다릅니다. 할당에 동의-이것을 벤치마킹해야합니다. 일반적으로 할당을 제거하는 것이 좋지만 YMMV를 찾는 것이 좋습니다.
mikera

4

Aaronaught훌륭한 답변에서 이미 언급 한 것 이외의 코드는 개발, 이해 및 디버깅이 매우 어려울 수 있습니다. 에서 언급 된 그 사람의 하나로서 "매우 효율적이지만 ... 그것은 ... 아주 쉽게 망치는" LMAX 블로그 .

  • 전통적인 쿼리 및 잠금에 익숙한 개발자 에게는 새로운 접근 방식을 코딩하는 것이 거친 말을 타는 것처럼 느껴질 수 있습니다. LMAX 테크니컬 페이퍼에 언급 된 페이저 를 실험 할 때의 경험은 적어도 저의 경험이었습니다 . 그런 의미에서 나는이 접근법이 개발자의 두뇌 경쟁에 대한 잠금 경합을 거래한다고 말할 것이다 .

위에서 언급 한 바와 같이, Disruptor 및 이와 유사한 접근 방식을 선택하는 사람들 은 솔루션 을 유지 하기에 충분한 개발 리소스를 확보 해야합니다.

전반적으로 Disruptor 접근 방식은 나에게 유망합니다. 회사가 위에서 언급 한 이유로이를 활용할 여유가없는 경우라도 경영진이 연구에 대한 노력 (및 일반적으로 SEDA) 에 "투자"를하도록 설득하는 것이 좋습니다 . 그렇지 않은 경우 언젠가는 고객은 4 배, 8 배 등 적은 서버를 필요로하는보다 경쟁력있는 솔루션을 선호합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.