성능 효율성 요소 - AWS 권장 가이드

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

성능 효율성 요소

AWS Well-Architected Framework의 성능 효율성 원칙은 데이터를 수집하거나 쿼리하는 동안 성능을 최적화하는 방법에 중점을 둡니다. 성능 최적화는 다음과 같은 점진적이고 지속적인 프로세스입니다.

  • 비즈니스 요구 사항 확인

  • 워크로드 성능 측정

  • 성능 저하 구성 요소 식별

  • 비즈니스 요구 사항에 맞게 구성 요소 조정

성능 효율성 원칙은 사용할 올바른 그래프 데이터 모델 및 쿼리 언어를 식별하는 데 도움이 될 수 있는 사용 사례별 지침을 제공합니다. 또한 Amazon Neptune에서 데이터를 수집하고 사용할 때 따라야 할 모범 사례도 포함되어 있습니다.

성능 효율성 원칙은 다음 주요 영역에 중점을 둡니다.

  • 그래프 모델링

  • 쿼리 최적화

  • 클러스터 오른쪽 크기 조정

  • 쓰기 최적화

그래프 모델링 이해

레이블 속성 그래프(LPG)와 리소스 설명 프레임워크(RDF) 모델의 차이점을 이해합니다. 대부분의 경우 이는 선호 사항입니다. 그러나 한 모델이 다른 모델보다 더 적합한 몇 가지 사용 사례가 있습니다. 그래프에서 두 노드를 연결하는 경로에 대한 지식이 필요한 경우 LPG를 선택합니다. Neptune 클러스터 또는 기타 그래프 트리플 스토어에 데이터를 페더레이션하려면 RDF를 선택합니다.

서비스형 소프트웨어(SaaS) 애플리케이션 또는 다중 테넌시가 필요한 애플리케이션을 구축하는 경우 각 클러스터에 대해 하나의 테넌트를 보유하는 대신 데이터 모델에 테넌트의 논리적 분리를 통합하는 것이 좋습니다. 이러한 유형의 설계를 달성하기 위해 고객 식별자를 레이블에 추가하거나 테넌트 식별자를 나타내는 속성 키-값 페어를 추가하는 등 SPARQL 명명된 그래프 및 레이블 지정 전략을 사용할 수 있습니다. 클라이언트 계층이 이러한 값을 주입하여 논리적 분리를 유지하는지 확인합니다.

쿼리의 성능은 쿼리 처리 시 평가해야 하는 그래프 객체(노드, 엣지, 속성) 수에 따라 달라집니다. 따라서 그래프 모델은 애플리케이션의 성능에 상당한 영향을 미칠 수 있습니다. 가능하면 세분화된 레이블을 사용하고 경로 결정 또는 필터링을 달성하는 데 필요한 속성만 저장합니다. 더 높은 성능을 얻으려면 요약 노드 생성 또는 공통 경로를 연결하는 더 직접적인 엣지 생성과 같이 그래프의 부분을 미리 계산하는 것이 좋습니다.

레이블이 동일한 엣지 수가 비정상적으로 많은 노드 간 이동을 피하십시오. 이러한 노드에는 종종 수천 개의 엣지가 있습니다(대부분의 노드에는 수십 개의 엣지 수가 있음). 그 결과 컴퓨팅 및 데이터 복잡성이 훨씬 높아집니다. 이러한 노드는 일부 쿼리 패턴에서 문제가 되지 않을 수 있지만, 특히 노드를 중간 단계로 탐색하는 경우 데이터를 다르게 모델링하여 이를 방지하는 것이 좋습니다. 느린 쿼리 로그를 사용하여 이러한 노드를 탐색하는 쿼리를 식별할 수 있습니다. 특히 디버그 모드를 사용하는 경우 평균 쿼리 패턴보다 훨씬 더 높은 지연 시간과 데이터 액세스 지표를 관찰할 수 있습니다.

사용 사례에서 NeptuneIDs에 대해 임의의 GUID 값을 할당하는 대신 노드 및 엣지에 결정적 노드 IDs 사용하는 경우 이를 사용합니다. ID로 노드에 액세스하는 것이 가장 효율적인 방법입니다.

쿼리 최적화

openCypher 및 Gremlin 언어는 LPG 모델에서 상호 교환적으로 사용할 수 있습니다. 성능이 가장 중요한 문제인 경우 특정 쿼리 패턴에 대해 한 언어가 다른 언어보다 성능이 좋을 수 있으므로 두 언어를 서로 바꿔 사용하는 것이 좋습니다.

Neptune은 대체 쿼리 엔진(DFE)으로 변환하는 중입니다. openCypher는 DFE에서만 실행되지만 쿼리 주석을 사용하여 선택적으로 Gremlin 및 SPARQL 쿼리를 DFE에서 실행하도록 설정할 수 있습니다. DFE가 활성화된 상태에서 쿼리를 테스트하고 DFE를 사용하지 않을 때 쿼리 패턴의 성능을 비교하는 것이 좋습니다.

Neptune은 전체 그래프를 평가하는 분석 쿼리가 아닌 단일 노드 또는 노드 세트에서 시작하고 거기에서 팬아웃하는 트랜잭션 유형 쿼리에 최적화되어 있습니다. 분석 쿼리 워크로드의 경우 Pandas용 AWS SDK를 사용하거나 또는 AWS Glue Amazon EMR과 결합된 neptune-export를 사용하는 것이 좋습니다.

모델 및 쿼리에서 비효율성과 병목 현상을 식별하려면 각 쿼리 언어에 대해 profileexplain APIs를 사용하여 쿼리 계획 및 쿼리 지표에 대한 자세한 설명을 얻습니다. 자세한 내용은 Gremlin 프로파일, openCypher 설명SPARQL 설명을 참조하세요.

쿼리 패턴을 이해합니다. 그래프의 개별 엣지 수가 커지면 기본 Neptune 액세스 전략이 비효율적이 될 수 있습니다. 다음 쿼리는 매우 비효율적일 수 있습니다.

  • 엣지 레이블이 지정되지 않은 경우 엣지를 역방향으로 탐색하는 쿼리입니다.

  • Gremlin.both()에서와 같이 내부적으로 동일한 패턴을 사용하는 절 또는 어떤 언어로든 노드를 삭제하는 절(라벨에 대한 지식 없이 들어오는 엣지를 삭제해야 함).

  • 속성 레이블을 지정하지 않고 속성 값에 액세스하는 쿼리입니다. 이러한 쿼리는 매우 비효율적일 수 있습니다. 이것이 사용 패턴과 일치하는 경우 OSGP 인덱스(객체, 제목, 그래프, 조건자)를 활성화하는 것이 좋습니다.

느린 쿼리 로깅을 사용하여 느린 쿼리를 식별합니다. 느린 쿼리는 최적화되지 않은 쿼리 계획 또는 불필요하게 많은 수의 인덱스 조회로 인해 발생할 수 있으며, 이로 인해 I/O 비용이 증가할 수 있습니다. Gremlin, SPARQL 또는 openCypher에 대한 Neptune 설명 및 프로필 엔드포인트는 이러한 쿼리가 느린 이유를 이해하는 데 도움이 될 수 있습니다. 원인에는 다음이 포함될 수 있습니다.

  • 그래프의 평균 노드에 비해 엣지 수가 비정상적으로 많은 노드(예: 수천 대 수십 개)는 계산 복잡성을 더하여 지연 시간이 길어지고 리소스 소비가 늘어날 수 있습니다. 이러한 노드가 올바르게 모델링되었는지 또는 통과해야 하는 엣지 수를 줄이기 위해 액세스 패턴을 개선할 수 있는지 확인합니다.

  • 최적화되지 않은 쿼리에는 특정 단계가 최적화되지 않았다는 경고가 포함됩니다. 최적화된 단계를 사용하도록 이러한 쿼리를 다시 작성하면 성능이 향상될 수 있습니다.

  • 중복 필터로 인해 불필요한 인덱스 조회가 발생할 수 있습니다. 마찬가지로 중복 패턴으로 인해 쿼리를 개선하여 최적화할 수 있는 중복 인덱스 조회가 발생할 수 있습니다(프로파일 출력Index Operations - Duplication ratio의 참조).

  • Gremlin과 같은 일부 언어는 숫자 값을 강력하게 입력하지 않으며 대신 형식 승격을 사용합니다. 예를 들어 값이 55인 경우 Neptune은 정수, 장수, 부동 소수점 및 55와 동등한 기타 숫자 유형인 값을 찾습니다. 이로 인해 추가 작업이 발생합니다. 유형이 일치하는 것을 미리 알고 있는 경우 쿼리 힌트를 사용하여 이를 방지할 수 있습니다.

  • 그래프 모델은 성능에 큰 영향을 미칠 수 있습니다. 보다 세분화된 레이블을 사용하거나 다중 홉 선형 경로에 대한 바로 가기를 미리 계산하여 평가해야 하는 객체 수를 줄이는 것이 좋습니다.

쿼리 최적화만으로 성능 요구 사항에 도달할 수 없는 경우 Neptune과 함께 다양한 캐싱 기술을 사용하여 이러한 요구 사항을 충족하는 것이 좋습니다.

적절한 크기의 클러스터

동시성 및 처리량 요구 사항에 맞게 클러스터의 크기를 조정합니다. 클러스터의 각 인스턴스에서 처리할 수 있는 동시 쿼리 수는 해당 인스턴스의 가상 CPUs(vCPUs) 수의 2배와 같습니다. 모든 작업자 스레드가 점유되는 동안 도착하는 추가 쿼리는 서버 측 대기열에 저장됩니다. 이러한 쿼리는 작업자 스레드를 사용할 수 있게 되면 first-in-first-out(FIFO) 방식으로 처리됩니다. MainRequestQueuePendingRequests Amazon CloudWatch 지표는 각 인스턴스의 현재 대기열 깊이를 보여줍니다. 이 값이 자주 0보다 크면 vCPUs가 더 많은 인스턴스를 선택하는 것이 좋습니다. 대기열 깊이가 8,192를 초과하면 Neptune은 ThrottlingException 오류를 반환합니다.

각 인스턴스에 대한 RAM의 약 65%가 버퍼 캐시용으로 예약되어 있습니다. 버퍼 캐시에는 작업 데이터 세트(전체 그래프가 아니라 쿼리 중인 데이터만)가 들어 있습니다. 스토리지 대신 버퍼 캐시에서 가져오는 데이터의 비율을 확인하려면 CloudWatch 지표를 모니터링합니다BufferCacheHitRatio. 이 지표가 종종 99.9% 미만으로 떨어지면 메모리가 더 많은 인스턴스를 시도하여 지연 시간 및 I/O 비용을 줄이는지 확인하는 것이 좋습니다.

읽기 전용 복제본은 라이터 인스턴스와 크기가 같을 필요는 없습니다. 그러나 쓰기 워크로드가 많으면 복제를 따라잡을 수 없기 때문에 작은 복제본이 뒤쳐지고 재부팅될 수 있습니다. 따라서 라이터 인스턴스보다 크거나 같은 복제본을 만드는 것이 좋습니다.

읽기 전용 복제본에 Auto Scaling을 사용하는 경우 새 읽기 전용 복제본을 온라인 상태로 전환하는 데 최대 15분이 걸릴 수 있습니다. 클라이언트 트래픽이 빠르지만 예측 가능하게 증가하면 예약된 조정을 사용하여 해당 초기화 시간을 고려하여 최소 읽기 전용 복제본 수를 더 높게 설정하는 것이 좋습니다.

서버리스 인스턴스는 여러 가지 사용 사례와 워크로드를 지원합니다. 다음 시나리오에서는 서버리스 오버 프로비저닝된 인스턴스를 고려합니다.

  • 워크로드는 하루 종일 변동하는 경우가 많습니다.

  • 새 애플리케이션을 생성했는데 워크로드 크기가 무엇인지 확실하지 않습니다.

  • 개발 및 테스트를 수행하고 있습니다.

서버리스 인스턴스는 RAM GB당 1달러 기준으로 동등한 프로비저닝된 인스턴스보다 비용이 더 높다는 점에 유의해야 합니다. 각 서버리스 인스턴스는 연결된 vCPU 및 네트워킹과 함께 2GB의 RAM으로 구성됩니다. 옵션 간에 비용 분석을 수행하여 서프라이즈 빌을 방지합니다. 일반적으로 매일 몇 시간 동안만 워크로드가 매우 많고 하루 중 나머지 시간이 거의 0이 되거나 하루 종일 워크로드가 크게 변동하는 경우에만 서버리스를 통해 비용을 절감할 수 있습니다.

쓰기 최적화

쓰기를 최적화하려면 다음을 고려하세요.

  • Neptune Bulk Loader는 데이터베이스를 처음 로드하거나 기존 데이터에 추가하는 최적의 방법입니다. Neptune 로더는 트랜잭션이 아니며 데이터를 삭제할 수 없으므로 요구 사항인 경우 사용하지 마세요.

  • 트랜잭션 업데이트는 지원되는 쿼리 언어를 사용하여 수행할 수 있습니다. 쓰기 I/O 작업을 최적화하려면 커밋당 50~100개의 객체 배치로 데이터를 작성합니다. 객체는 LPG의 노드 또는 엣지에 있는 노드, 엣지 또는 속성, RDF의 트리플 스토어 또는 쿼드입니다.

  • 모든 Neptune 쓰기 작업은 각 연결에 대해 단일 스레드입니다. 대량의 데이터를 Neptune으로 전송할 때는 각각 데이터를 쓰는 여러 병렬 연결을 사용하는 것이 좋습니다. Neptune 프로비저닝된 인스턴스를 선택하면 인스턴스 크기가 여러 vCPUs와 연결됩니다. Neptune은 인스턴스의 각 vCPU에 대해 두 개의 데이터베이스 스레드를 생성하므로 최적의 병렬화를 테스트할 때 vCPUs 수의 두 배에서 시작합니다.   서버리스 인스턴스는 4개의 NCUs마다 약 1개의 속도로 vCPUs 수를 조정합니다.

  • 한 번의 연결만으로 언제든지 데이터를 쓰는 경우에도 모든 쓰기 프로세스 중에 ConcurrentModificationExceptions를 계획하고 효율적으로 처리합니다. 클라이언트가 ConcurrentModificationExceptions 발생할 때 안정성을 제공하도록 설계합니다.

  • 모든 데이터를 삭제하려면 동시 삭제 쿼리를 실행하는 대신 빠른 재설정 API를 사용하는 것이 좋습니다. 후자의 경우 이전보다 훨씬 더 오래 걸리며 상당한 I/O 비용이 발생합니다.

  • 대부분의 데이터를 삭제하려면 neptune-export를 사용하여 데이터를 새 클러스터로 로드하여 유지하려는 데이터를 내보내는 것이 좋습니다. 그런 다음 원래 클러스터를 삭제합니다.