Share via


성능에 대한 모델링

대부분의 경우 모델링 방식은 애플리케이션의 성능에 큰 영향을 미칠 수 있습니다. 제대로 정규화되고 "올바른" 모델은 일반적으로 좋은 시작점이지만 실제 애플리케이션에서는 몇 가지 실용적인 손상이 좋은 성능을 달성하기 위해 먼 길을 갈 수 있습니다. 애플리케이션이 프로덕션 환경에서 실행되면 모델을 변경하는 것이 매우 어렵기 때문에 초기 모델을 만들 때 성능을 염두에 두어야 합니다.

비정규화 및 캐싱

비정규화는 일반적으로 쿼리할 때 조인을 제거하기 위해 스키마에 중복 데이터를 추가하는 방법입니다. 예를 들어 각 게시물에 평점이 있는 블로그 및 게시물이 있는 모델의 경우 블로그의 평균 등급을 자주 표시해야 할 수 있습니다. 이에 대한 간단한 방법은 블로그별로 게시물을 그룹화하고 평균을 쿼리의 일부로 계산합니다. 그러나 이렇게 하려면 두 테이블 간에 비용이 많이 드는 조인이 필요합니다. 비정규화는 블로그의 새 열에 모든 게시물의 계산된 평균을 추가하여 조인하거나 계산하지 않고 즉시 액세스할 수 있도록 합니다.

위의 내용은 캐싱 형식으로 볼 수 있습니다. 게시물의 집계 정보는 블로그에 캐시됩니다. 캐싱과 마찬가지로 캐시된 값을 캐싱하는 데이터로 최신 상태로 유지하는 방법이 문제입니다. 대부분의 경우 캐시된 데이터가 약간 지연되는 것이 좋습니다. 예를 들어 위의 예제에서는 일반적으로 블로그의 평균 등급이 지정된 지점에서 완전히 최신 상태로 유지되지 않는 것이 합리적입니다. 이 경우 때때로 다시 계산할 수 있습니다. 그렇지 않으면 캐시된 값을 최신 상태로 유지하기 위해 보다 정교한 시스템을 설정해야 합니다.

다음은 EF Core의 비정규화 및 캐싱에 대한 몇 가지 기술을 자세히 설명하며 설명서의 관련 섹션을 가리킵니다.

저장된 계산 열

캐시할 데이터가 동일한 테이블에 있는 다른 열의 곱인 경우 저장된 계산 열이 완벽한 솔루션이 될 수 있습니다. 예를 들어 Customer에는 FirstNameLastName 열이 있을 수 있지만 고객의 전체 이름을 기준으로 검색해야 할 수 있습니다. 저장된 계산 열은 행이 변경될 때마다 다시 계산하는 데이터베이스에서 자동으로 유지 관리되며, 쿼리 속도를 높이기 위해 인덱스를 정의할 수도 있습니다.

입력이 변경되면 캐시 열 업데이트

캐시된 열이 테이블 행 외부의 입력을 참조해야 하는 경우 계산 열을 사용할 수 없습니다. 그러나 입력이 변경되면 열을 다시 계산할 수 있습니다. 예를 들어 게시물이 변경, 추가 또는 제거될 때마다 평균 블로그의 등급을 다시 계산할 수 있습니다. 다시 계산이 필요할 때 정확한 조건을 식별해야 합니다. 그렇지 않으면 캐시된 값이 동기화되지 않습니다.

이 작업을 수행하는 한 가지 방법은 일반 EF Core API를 통해 직접 업데이트를 수행하는 것입니다. SaveChanges이벤트 또는 인터셉터를 사용하여 게시물이 업데이트되고 있는지 자동으로 확인하고 다시 계산을 수행할 수 있습니다. 일반적으로 추가 명령을 보내야 하므로 추가 데이터베이스 왕복이 수반됩니다.

더 많은 성능 중요한 애플리케이션의 경우 데이터베이스 트리거를 정의하여 데이터베이스에서 다시 계산을 자동으로 수행할 수 있습니다. 이렇게 하면 추가 데이터베이스 왕복이 저장되고, 주 업데이트와 동일한 트랜잭션 내에서 자동으로 발생하며, 설정하는 것이 더 간단할 수 있습니다. EF는 트리거를 만들거나 유지 관리하기 위한 특정 API를 제공하지 않지만 빈 마이그레이션을 만들고 원시 SQL을 통해 트리거 정의를 추가하는 것은 완벽합니다.

구체화/인덱싱된 뷰

구체화된(또는 인덱싱된) 뷰는 보기가 쿼리될 때마다 계산되지 않고 데이터가 디스크에 저장된다는 점을 제외하고 일반 보기와 비슷합니다. 이러한 뷰는 잠재적으로 비용이 많이 드는 계산 결과를 캐시하므로 저장된 계산 열과 개념적으로 비슷합니다. 그러나 단일 열 대신 전체 쿼리 결과 집합을 캐시합니다. 구체화된 뷰는 일반 테이블과 마찬가지로 쿼리할 수 있으며 디스크에 캐시되므로 이러한 쿼리는 뷰를 정의하는 쿼리의 비용이 많이 드는 계산을 지속적으로 수행할 필요 없이 매우 빠르고 저렴하게 실행됩니다.

구체화된 뷰에 대한 특정 지원은 데이터베이스마다 다릅니다. 일부 데이터베이스(예: PostgreSQL)에서는 해당 값이 기본 테이블과 동기화되도록 구체화된 뷰를 수동으로 새로 고쳐야 합니다. 이 작업은 일반적으로 타이머(일부 데이터 지연이 허용되는 경우) 또는 특정 조건에서 트리거 또는 저장 프로시저 호출을 통해 수행됩니다. 반면 SQL Server 인덱싱된 뷰는 기본 테이블이 수정되면 자동으로 업데이트됩니다. 이렇게 하면 더 느린 업데이트의 비용으로 뷰에 항상 최신 데이터가 표시됩니다. 또한 SQL Server 인덱스 뷰에는 지원되는 내용에 대한 다양한 제한 사항이 있습니다. 자세한 내용은 설명서를 참조하세요.

EF는 현재 구체화/인덱싱되거나 그렇지 않은 경우 뷰를 만들거나 유지 관리하기 위한 특정 API를 제공하지 않습니다. 그러나 빈 마이그레이션을 만들고 원시 SQL을 통해 뷰 정의를 추가하는 것은 완벽합니다.

상속 매핑

이 섹션을 계속하기 전에 상속에 대한 전용 페이지를 읽는 것이 좋습니다.

EF Core는 현재 상속 모델을 관계형 데이터베이스에 매핑하는 세 가지 기술을 지원합니다.

  • 클래스의 전체 .NET 계층 구조가 단일 데이터베이스 테이블에 매핑되는 TPH(테이블 단위 계층 구조)입니다.
  • .NET 계층 구조의 각 형식이 데이터베이스의 다른 테이블에 매핑되는 TPT(형식당 하나의 테이블)입니다.
  • .NET 계층 구조의 각 구체적인 형식이 데이터베이스의 다른 테이블에 매핑되는 TPC(콘크리트당 하나의 테이블)입니다. 여기서 각 테이블에는 해당 형식의 모든 속성에 대한 열이 포함됩니다.

상속 매핑 기술의 선택은 애플리케이션 성능에 상당한 영향을 미칠 수 있습니다. 선택에 커밋하기 전에 신중하게 측정하는 것이 좋습니다.

직관적으로 TPT는 "클리너" 기술처럼 보일 수 있습니다. 각 .NET 형식에 대한 별도의 테이블을 사용하면 데이터베이스 스키마가 .NET 형식 계층 구조와 유사하게 표시됩니다. 또한 TPH는 단일 테이블의 전체 계층 구조를 나타내야 하므로 행에는 실제로 행에 보관되는 형식에 관계없이 모든 열이 있으며 관련 없는 열은 항상 비어 있고 사용되지 않습니다. "부정한" 매핑 기술로 보이는 것 외에도 많은 사람들이 이러한 빈 열이 데이터베이스에서 상당한 공간을 차지하며 성능도 저하될 수 있다고 믿습니다.

데이터베이스 시스템에서 지원하는 경우(예: SQL Server) 거의 채워지지 않는 TPH 열에 "스파스 열"을 사용하는 것이 좋습니다.

그러나 측정은 TPT가 대부분의 경우 성능 관점에서 열등한 매핑 기술임을 보여 줍니다. TPH의 모든 데이터가 단일 테이블에서 제공되는 경우 TPT 쿼리는 여러 테이블을 함께 조인해야 하며 조인은 관계형 데이터베이스의 성능 문제의 기본 원본 중 하나입니다. 또한 데이터베이스는 일반적으로 빈 열을 잘 처리하는 경향이 있으며 SQL Server 스파스 열과 같은 기능은 이 오버헤드를 더욱 줄일 수 있습니다.

TPC는 TPH와 성능 특성이 비슷하지만 여러 테이블이 포함되므로 모든 형식의 엔터티를 선택할 때 약간 느립니다. 그러나 TPC는 단일 리프 형식의 엔터티를 쿼리할 때 정말 뛰어납니다. 쿼리는 단일 테이블만 사용하고 필터링이 필요하지 않습니다.

구체적인 예제는 7형 계층 구조로 간단한 모델을 설정하는 이 벤치마크를 참조하세요. 각 형식(총 35,000개 행)에 대해 5,000개의 행이 시드되며 벤치마크는 데이터베이스의 모든 행을 로드합니다.

메서드 평균 오류 StdDev Gen 0 Gen 1 Allocated
TPH 149.0밀리초 3.38밀리초 9.80밀리초 4000.0000 1000.0000 40MB
TPT 312.9밀리초 6.17밀리초 10.81밀리초 9000.0000 3000.0000 75MB
TPC 158.2밀리초 3.24밀리초 8.88밀리초 5000.0000 2000.0000 46MB

볼 수 있듯이 TPH 및 TPC는 이 시나리오의 TPT보다 훨씬 효율적입니다. 실제 결과는 항상 실행 중인 특정 쿼리와 계층 구조의 테이블 수에 따라 달라지므로 다른 쿼리는 다른 성능 차이를 표시할 수 있습니다. 다른 쿼리를 테스트하기 위한 템플릿으로 이 벤치마크 코드를 사용하는 것이 좋습니다.