ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 동시성 토큰
    프로그래밍/기록, 개념, 용어 2022. 10. 2. 00:04
    반응형

    EF Core 같은 ORM에선 각 레코드에 대한 동시성 제어를 어떻게 처리할까 궁금했는데, 찾아보니 동시성 토큰(Concurrency Tokens) 이라는 게 있었다.

    Concurrency Tokens - EF Core | Microsoft Learn

    동시성 토큰을 이용하면 애플리케이션에서 제어 중인 레코드와 DB에서의 레코드가 같을 때만 업데이트, 같지 않다면 업데이트를 하지 않고 예외를 발생시킴으로서 낙관적으로 동시성 제어를 할 수 있게 된다.

    다음은 동시성 토큰을 지정하는 방법이다.

    이렇게 동시성 토큰을 지정하면 행이 인서트 또는 업데이트 될 때마다 DB에 의해 자동으로 Timestamp의 값이 변경 된다.

    class Person
    {
      [Key]
      public int Id { get; set; }
      
      public int Age { get; set; }
      
      // 동시성 토큰으로 사용할 프로퍼티 설정.
      // 레코드가 인서트 또는 업데이트 될 때마다,
      // 이 프로퍼티는 DB에 의해 자동으로 값이 바뀐다.
      [Timestamp]
      public byte[] Timestamp { get; set; }
    }
    
    // ...
    
    person.Age = 99;
    
    await context.SaveChangesAsync();

    그리고 SaveChanges 할 때, EF Core는 아래처럼 Timestamp이 애플리케이션에서의 버전과 DB에서의 버전이 같은지 체크하고 업데이트하는 쿼리를 실행한다.

    UPDATE People
    SET Age = @p0
    WHERE Id = @p1 AND Timestamp = @p2

     

    레코드의 Timestamp 필드 불일치로 인해 업데이트가 실패하면 DBUpdateConcurrencyException이 발생한다.

    이대로 사용자에게는 요청을 실패로 끝내도 되고,

    또는 애플리케이션에서 제어하는 값과 DB에 있는 값을 수정하고 다시 업데이트를 시도할 수도 있다.

    try
    {
      // ...
    }
    catch (DBUpdateConcurrencyException e)
    {
      foreach (var entry in e.Entries)
      {
        var proposedValues = entry.CurrentValues; // 애플리케이션 내에서 설정된 값들
        var databaseValues = entry.GetDatabaseValues(); // 현재 DB에 있는 값들
        
        foreach (var property in proposedValues.Properties)
        {
          var proposedValue = proposedValues[property]; // 애플리케이션 내에서 설정된 값
          var databaseValue = databaseValues[property]; // DB에 있는 값
          
          // 애플리케이션 내의 값을 DB에 있는 값으로 맞춤.
          // 아래 구문은 모든 값을 databaseValue로 설정하므로,
          // 애플리케이션 내에서 설정된 값이 DB의 값으로 바뀌게 된다.
          // proposedValues[property] = databaseValue;
        }
        
        // 원본 값들을 DB의 값들(라인 10)로 변경한다.
        // Timestamp(RowVersion)에 해당하는 프로퍼티가 DB의 것과 맞춰지게 된다.
        // SaveChanges() 시 비교하는 Timestamp는 원본 것과 비교를 하므로,
        // 이 동작으로 인해 DBUpdateConcurrency를 우회할 수 있게 된다.
        entry.OriginalValues.SetValues(databaseValues);
      }
    }

    EF Core의 경우 SaveChanges 에서 발생하는 쿼리들 단위로 트랜잭션이 적용되는 것이 기본 동작인데, 위 성질을 동시성 토큰과 함께 이용하면 대부분의 동시성 대처는 가능할 것으로 생각된다.

    기본적으로 데이터베이스 공급자가 트랜잭션을 지원하는 경우 SaveChanges에 대한 단일 호출의 모든 변경 내용이 트랜잭션에 적용됩니다. 변경이 실패하면 트랜잭션이 롤백되고 변경 내용이 데이터베이스에 적용되지 않습니다. 즉, SaveChanges이 완전히 성공하도록 보장되거나 오류가 발생하는 경우 데이터베이스가 수정되지 않은 상태로 유지됩니다.
    대부분의 애플리케이션에서는 이 기본 동작이면 충분합니다. 애플리케이션 요구 사항에서 필요하다고 생각되는 경우에만 트랜잭션을 수동으로 제어해야 합니다.

    위 까지는 Timestamp(또는 RowVersion이라 불리는)를 이용한 동시성 체크이고, 그 외 개별 프로퍼티에 동시성 체크 애트리뷰트를 적용하는 방법도 있다.

    다만 그 방법은 권장하지 않는다고 문서에는 나와있다.

    모델의 속성에 [ConcurrencyCheck] 또는IsConcurrencyToken 적용 이 방법은 권장하지 않습니다. 자세한 내용은 EF Core의 동시성 토큰을 참조하세요.

     

    참고

    반응형
Designed by Tistory.