성능 진단

이 섹션에서는 EF 애플리케이션에서 성능 문제를 검색하는 방법 및 문제가 있는 영역이 식별되면 이를 추가로 분석하여 근본 문제를 식별하는 방법을 설명합니다. 어떤 결론으로 넘어가기 전에 신중하게 문제를 진단 및 조사하고 문제의 근본 원인을 가정하지 않는 것이 중요합니다.

로깅을 통해 느린 데이터베이스 명령 식별

EF는 하루 일과가 마무리되면 데이터베이스에 대해 실행할 명령을 준비하고 실행합니다. 즉, 관계형 데이터베이스의 경우 ADO.NET 데이터베이스 API를 통해 SQL 문을 실행합니다. 특정 쿼리에 너무 많은 시간이 걸리는 경우(예: 인덱스가 없기 때문에) 이는 명령 실행 로그를 검사하고 실제 걸리는 시간을 관찰하여 검색할 수 있습니다.

EF를 사용하면 간단한 로깅 또는 Microsoft.Extensions.Logging을 통해 명령 실행 시간을 매우 쉽게 캡처할 수 있습니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;ConnectRetryCount=0")
        .LogTo(Console.WriteLine, LogLevel.Information);
}

로깅 수준이 LogLevel.Information에서 설정되면 EF는 소요된 시간과 함께 각 명령 실행에 대한 로그 메시지를 내보낸다.

info: 06/12/2020 09:12:36.117 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b].[Id], [b].[Name]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'foo'

위의 명령은 4밀리초가 걸렸습니다. 특정 명령이 예상보다 시간이 많이 걸리는 경우 성능 문제에 대한 가능한 원인을 찾았으며 이제 성능 문제가 느리게 실행되는 이유를 이해하는 데 집중할 수 있습니다. 명령 로깅은 예기치 않은 데이터베이스 왕복이 수행되는 경우를 표시할 수도 있으며, 이 경우 하나만 필요한 여러 명령으로 표시됩니다.

Warning

프로덕션 환경에서 명령 실행 로깅을 사용하도록 설정하는 것은 일반적으로 잘못된 생각입니다. 로깅 자체는 애플리케이션의 속도를 늦추고 서버의 디스크를 채울 수 있는 거대한 로그 파일을 빠르게 만들 수 있습니다. 애플리케이션을 신중하게 모니터링하는 동안 데이터를 수집하거나 사전 프로덕션 시스템에서 로깅 데이터를 캡처하기 위해 짧은 시간 동안만 로그온을 유지하는 것이 좋습니다.

데이터베이스 명령과 LINQ 쿼리의 상관 관계 지정

명령 실행 로깅의 한 가지 문제는 SQL 쿼리와 LINQ 쿼리의 상관 관계를 지정하기가 어려울 수 있다는 것입니다. EF에서 실행된 SQL 명령은 생성된 LINQ 쿼리와 매우 다를 수 있습니다. 이 문제를 해결하려면 SQL 쿼리에 작은 식별 주석을 삽입할 수 있는 EF의 쿼리 태그 기능을 사용할 수 있습니다.

var myLocation = new Point(1, 2);
var nearestPeople = (from f in context.People.TagWith("This is my spatial query!")
                     orderby f.Location.Distance(myLocation) descending
                     select f).Take(5).ToList();

태그가 로그에 표시됩니다.

-- This is my spatial query!

SELECT TOP(@__p_1) [p].[Id], [p].[Location]
FROM [People] AS [p]
ORDER BY [p].[Location].STDistance(@__myLocation_0) DESC

명령 실행 로그를 즉시 읽을 수 있도록 이러한 방식으로 애플리케이션의 주요 쿼리에 태그를 지정하는 것이 좋습니다.

성능 데이터를 캡처하기 위한 기타 인터페이스

명령 실행 시간을 캡처하기 위한 EF의 로깅 기능에 대한 다양한 대안이 있으며, 이는 더 강력할 수 있습니다. 일반적으로 데이터베이스에는 간단한 실행 시간 이외에 훨씬 더 풍부한 데이터베이스 관련 정보를 제공하는 자체 추적 및 성능 분석 도구가 제공되며, 실제 설정, 기능 및 사용량은 데이터베이스마다 상당히 다릅니다.

예를 들어 SQL Server Management Studio는 SQL Server 인스턴스에 연결하고 중요한 관리 및 성능 정보를 제공할 수 있는 강력한 클라이언트입니다. 세부 정보로 이동하는 것은 이 섹션의 범위를 벗어나지만 서버 작업의 라이브 대시보드(가장 비용이 많이 드는 쿼리 포함)를 제공하는 작업 모니터 및 정확한 요구 사항에 맞게 조정될 수 있는 임의의 데이터 캡처 세션을 정의할 수 있는 XEvent(확장 이벤트) 기능은 언급할 만한 가치가 있습니다. 모니터링에 대한 SQL Server 설명서에서는 이러한 기능뿐만 아니라 다른 기능에 대한 자세한 정보를 제공합니다.

성능 데이터를 캡처하는 또 다른 접근 방식은 DiagnosticSource 인터페이스를 통해 EF 또는 데이터베이스 드라이버에서 자동으로 내보낸 정보를 수집한 다음 해당 데이터를 분석하거나 대시보드에 표시하는 것입니다. Azure를 사용하는 경우 Azure 애플리케이션 Insights는 웹 요청이 얼마나 빨리 처리되는지 분석할 때 데이터베이스 성능 및 쿼리 실행 시간을 통합하는 강력한 모니터링을 기본적으로 제공합니다. 이에 대한 자세한 내용은 Application Insights 성능 자습서Azure SQL 분석 페이지에서 확인할 수 있습니다.

쿼리 실행 계획 검사

최적화가 필요한 문제가 있는 쿼리를 정확히 파악한 후 다음 단계는 일반적으로 쿼리의 실행 계획을 분석하는 것입니다. 데이터베이스는 SQL 문을 수신하면 일반적으로 해당 계획을 실행하는 방법에 대한 계획을 생성합니다. 경우에 따라 정의된 인덱스, 테이블에 존재하는 데이터의 양 등에 따라 복잡한 의사 결정이 필요합니다(또한 계획 자체는 일반적으로 최적의 성능을 위해 서버에서 캐시되어야 함). 관계형 데이터베이스는 일반적으로 쿼리의 여러 부분에 대해 계산된 비용과 함께 사용자가 쿼리 계획을 볼 수 있는 방법을 제공하며, 이는 쿼리를 개선하는 데 매우 중요합니다.

SQL Server에서 시작하려면 쿼리 실행 계획에 대한 설명서를 참조하세요. 일반적인 분석 워크플로는 SQL Server Management Studio를 사용하고, 위의 수단 중 하나를 통해 식별된 느린 쿼리의 SQL을 붙여넣은 후 그래픽 실행 계획을 생성하는 것입니다.

SQL Server 실행 계획 표시

실행 계획은 처음에는 복잡해 보일 수 있지만 익숙해지는 데 약간의 시간을 할애할 가치가 있습니다. 계획의 각 노드와 관련된 비용을 확인하고 다양한 노드에서 인덱스가 사용되거나 사용되지 않는 방식을 식별하는 것이 특히 중요합니다.

위의 정보는 SQL Server와 관련이 있지만 다른 데이터베이스는 일반적으로 유사한 시각화를 통해 동일한 종류의 도구를 제공합니다.

Important

데이터베이스는 데이터베이스의 실제 데이터에 따라 서로 다른 쿼리 계획을 생성하는 경우가 있습니다. 예를 들어 테이블에 몇 개의 행만 포함된 경우 데이터베이스는 해당 테이블의 인덱스를 사용하지 않고 전체 테이블 검색을 대신 수행하도록 선택할 수 있습니다. 테스트 데이터베이스에서 쿼리 계획을 분석하는 경우 항상 프로덕션 시스템과 유사한 데이터가 포함되어 있는지 확인합니다.

메트릭

위의 섹션에서는 명령에 대한 정보를 가져오는 방법과 이러한 명령이 데이터베이스에서 실행되는 방법에 중점을 두었다. 그 외에도 EF는 EF 자체 내에서 발생하는 작업과 애플리케이션에서 사용되는 방법에 대한 더 낮은 수준의 정보를 제공하는 메트릭 집합을 노출합니다. 이러한 메트릭은 지속적인 재컴파일, 처리되지 않은 DbContext 누출 등을 유발하는 쿼리 캐싱 문제와 같은 특정 성능 문제와 성능 이상을 진단하는 데 매우 유용할 수 있습니다.

자세한 내용은 EF의 메트릭에 대한 전용 페이지를 참조하세요.

EF Core를 사용하여 벤치마킹

하루 일과가 마무리되면 쿼리를 작성하거나 실행하는 특정 방법이 다른 방법보다 빠른지 여부를 알아야 하는 경우가 있습니다. 대답을 가정하거나 추측하지 않는 것이 중요하며, 답변을 얻기 위해 빠른 벤치마크를 함께 구성하는 것은 매우 쉽습니다. 벤치마크를 작성하는 경우 사용자가 자신의 벤치마크를 작성하려고 할 때 발생하는 많은 문제를 처리하는 잘 알려진 BenchmarkDotNet 라이브러리를 사용하는 것이 좋습니다. 몇 가지 준비 반복을 수행했나요? 벤치마크가 실제로 실행되는 반복은 몇 번이고 그 이유는 무엇인가요? EF Core를 사용하는 벤치마크를 살펴보겠습니다.

아래 원본에 대한 전체 벤치마크 프로젝트는 여기에서 사용할 수 있습니다. 이를 복사하여 자체 벤치마크에 대한 템플릿으로 사용하는 것이 좋습니다.

간단한 벤치마크 시나리오에서는 데이터베이스에 있는 모든 블로그의 평균 순위를 계산하는 다음과 같은 다양한 방법을 비교해 보겠습니다.

  • 모든 엔터티를 로드하고, 개별 순위를 합산하고, 평균을 계산합니다.
  • 위와 마찬가지로 비추적 쿼리만 사용합니다. ID 확인이 수행되지 않고 엔터티가 변경 내용 추적을 위해 스냅샷되지 않으므로 더 빨라야 합니다.
  • 순위만 프로젝팅하여 전체 블로그 엔터티 인스턴스를 로드하지 마세요. 그러면 블로그 엔터티 형식의 불필요한 다른 열을 전송할 수 없게 됩니다.
  • 쿼리의 일부로 만들어 데이터베이스의 평균을 계산합니다. 모든 항목이 데이터베이스에서 계산되고 결과만 클라이언트로 다시 전송되므로 이는 가장 빠른 방법이어야 합니다.

BenchmarkDotNet을 사용하면 단위 테스트와 유사한 간단한 방법으로 벤치마킹할 코드를 작성하고 BenchmarkDotNet은 충분한 수의 반복을 위해 각 메서드를 자동으로 실행하여 소요 시간과 할당된 메모리 양을 안정적으로 측정합니다. 다음은 다양한 메서드입니다(전체 벤치마크 코드는 여기에서 확인할 수 있음).

[Benchmark]
public double LoadEntities()
{
    var sum = 0;
    var count = 0;
    using var ctx = new BloggingContext();
    foreach (var blog in ctx.Blogs)
    {
        sum += blog.Rating;
        count++;
    }

    return (double)sum / count;
}

BenchmarkDotNet에서 인쇄한 결과는 다음과 같습니다.

메서드 평균 오류 StdDev 중앙값 비율 RatioSD Gen 0 Gen 1 Gen 2 Allocated
LoadEntities 2,860.4 us 54.31 us 93.68 us 2,844.5 us 4.55 0.33 210.9375 70.3125 - 1309.56 KB
LoadEntitiesNoTracking 1,353.0 us 21.26 us 18.85 us 1,355.6 us 2.10 0.14 87.8906 3.9063 - 540.09 KB
ProjectOnlyRanking 910.9 us 20.91 us 61.65 us 892.9 us 1.46 0.14 41.0156 0.9766 - 252.08 KB
CalculateInDatabase 627.1 us 14.58 us 42.54 us 626.4 us 1.00 0.00 4.8828 - - 33.27 KB

참고 항목

메서드가 메서드 내에서 컨텍스트를 인스턴스화하고 삭제할 때 이러한 작업은 벤치마크에 대해 계산되지만 엄밀히 말하면 쿼리 프로세스의 일부가 아닙니다. 컨텍스트 인스턴스화와 삭제가 동일하기 때문에 두 대안을 서로 비교하는 것이 목표인지는 중요하지 않으며 전체 작업에 대해 보다 전체적인 측정을 제공합니다.

BenchmarkDotNet의 한 가지 제한 사항은 제공하는 메서드의 간단한 단일 스레드 성능을 측정하므로 동시 시나리오를 벤치마킹하는 데 적합하지 않다는 것입니다.

Important

항상 벤치마킹할 때 프로덕션 데이터와 유사한 데이터가 데이터베이스에 있어야 합니다. 그렇지 않으면 벤치마크 결과가 프로덕션의 실제 성능을 나타내지 않을 수 있습니다.