Delete batch using Entity Framework Core

Entity Framework Core: Bulk updates 를 읽고, 어느 정도 성능향상이 있을까 궁금해서 간략하게 코드를 작성해서 실행 시간을 측정했습니다.

프로젝트 설명

환경

  • Visual Studio 2022 (v17.0.4)
  • Localdb (appsettings.json 에서 연결문자열을 확인하세요.)
  • .NET 6
  • EntityFrameworkCore v6.0.1

마이그레이션

엔티티를 변경해서 확인하려면 아래 명령을 참조해서 마이그레이션 코드를 추가해야 합니다.

# DataContext 가 포함된 프로젝트 디렉터리로 이동합니다.
$ cd src/Sample.Data
# 마이그레이션 코드를 작성합니다.
$ dotnet ef migrations add "Initialize database" --context AppDbContext --startup-project ../Sample.App --project ../Sample.Data.SqlServer --json

확인

행 추가 (#1-1, #1-2)

-- Declaring variables 

DECLARE @inserted0 TABLE ([Id] bigint, [_Position] [int]);

MERGE [UserToken] USING (
VALUES (@p0, @p1, @p2, @p3, 0),
(@p4, @p5, @p6, @p7, 1),
(@p8, @p9, @p10, @p11, 2),
-- 생략
(@p160, @p161, @p162, @p163, 40),
(@p164, @p165, @p166, @p167, 41)) AS i ([CreatedAt], [ExpiresAt], [Purpose], [Token], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([CreatedAt], [ExpiresAt], [Purpose], [Token])
VALUES (i.[CreatedAt], i.[ExpiresAt], i.[Purpose], i.[Token])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;

행 삭제 (#2)

DELETE 문이 각 행별로 작성되어 실행됩니다.

1,000 행을 대상으로 약 507 밀리초가 소요되었습니다.

var deleteCandidate = Context.UserTokens.Where(x => x.ExpiresAt <= DateTime.UtcNow);

Context.UserTokens.RemoveRange(deleteCandidate);

await Context.SaveChangesAsync();
-- Declaring variables 

DELETE FROM [UserToken]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;

DELETE FROM [UserToken]
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

DELETE FROM [UserToken]
WHERE [Id] = @p2;
SELECT @@ROWCOUNT;

-- 생략

행 삭제 (#4)

SQL 문으로 삭제 명령을 정의하고, 실행합니다.

ANSI sql 문으로 작성하면 DBMS 의존을 줄일 수 있다고 생각합니다.

1,000 행을 대상으로 약 17 밀리초가 소요되었습니다.

var sql = string.Empty;
sql += $"DELETE FROM {nameof(UserToken)} ";
sql += $" WHERE {nameof(UserToken.ExpiresAt)} <= GetUtcDate()";

await Context.Database.ExecuteSqlRawAsync(sql);

SQL 문 실행시 트랜잭션 확인 (#3)

SQL 문으로 명령을 실행할 때, 트랜잭션을 확인합니다.

SQL 문의 결과가 커밋되지 않고 정상적으로 롤백됨을 확인했습니다.

using (var transaction = Context.Database.BeginTransaction())
{
    try
    {
        await Context.Database.ExecuteSqlRawAsync(sql);
        throw new Exception("Test");
        await transaction.CommitAsync();
    }
    catch (Exception)
    {
        await transaction.RollbackAsync();
    }
}

마침

일괄 작업으로 변경, 삭제 작업을 실행할 때, 성능향상을 위해 SQL 문을 고려할 필요가 있을 것 같습니다.

만료된 토큰 정리, 로그 정리 등에 사용하면 좋을 것으로 생각됩니다.

저장소

GitHub: sample.ef.batch

이 사이트는 광고를 포함하고 있습니다.
광고로 발생한 수익금은 서버 유지 관리에 사용되고 있습니다.

This site contains advertisements.
Revenue generated by the ad servers are being used for maintenance.

댓글 남기기