Whiteship's Note


Isolation 단계 더 높이기

Hibernate/Chapter 10 : 2008.03.30 00:25


Explicit pessimistic locking

  • 격리 수준을 read comitted 보다 높게 설정하는 것은 애플리케이션의 확장성을 고려할 때 좋치 않다.
  • Persistence context cache가 repeatable read를 제공하긴 하지만 이걸로 항상 만족스럽지 않을 수도 있다.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Item i = (Item) session.get(Item.class, 123);
String description = (String)
session.createQuery("select i.description from Item i" +
" where i.id = :itemid")
.setParameter("itemid", i.getId() )
.uniqueResult();
tx.commit();
session.close();
  • 위의 코드는 DB에서 같은 데이터를 두 번 읽어온다. 이 때 isolation level이 read committed 였다면, 두 번째에 읽어오는 값이 처음 읽어온 데이터와 다를 수 있다.(둘 사이에 어떤 트랜잭션이 해당하는 값을 바꾸고 커밋했을 수 있다.)
  • 전체 트랜잭션의 isolation level을 높이는 것이 아니라 lock() 메소드를 사용하여 해당하는 부분의 트랜잭젼의 isolation level을 높일 수 있다.
Session session = sessionFactory.openSession(); 
Transaction tx = session.beginTransaction();
Item i = (Item) session.get(Item.class, 123);

session.lock(i, LockMode.UPGRADE);

String description = (String)
session.createQuery("select i.description from Item i" +
" where i.id = :itemid")
.setParameter("itemid", i.getId() )
.uniqueResult();
tx.commit();
session.close();
  • 위의 LockMode.UPGRADE 는 item 객체 대응하는 레코드의 pessimistic lock을 가지고 다니게 된다.
Item i = (Item) session.get(Item.class, 123, LockMode.UPGRADE);
  • 위와같이 코드를 한 줄 줄일 수도 있다.
  • LockMode.UPGRADE는 롹을 가져올 때까지 대기하게 된다. 대기 시간은 사용하는 DB에 따라 다르다.
  • LockMode.NOWAIT는 롹이 없으면 기다리지 않고 쿼리가 바로 fail하도록 한다.

The Hibernate lock modes

  • LockMode.NONE - 락 사용하지 않음. 캐시에 객체가 존재하면 그 객체를 사용.
  • LockMode.READ - 모든 캐시를 무시하고 현재 메모리에 있는 엔티티의 버전과 실제 DB에 있는 버전이 같은지 확인한다.
  • LockMode.UPGRADE - LockMode.READ가 하는 일에 더해서 DB에서 pessimistic upgrade lock을 가져온다. SELECT ... FOR UPDATE 문을 지원하지 않는 DB를 사용할 때는 자동으로 LockMode.READ로 전환된다.
  • LockMode.UPGRADE_NOWAIT - UPDGRADE와 동일한데, SELECT ... FOR UPDATE NOWAIT를 사용한다. 락이 없으면 바로 예외를 던진다. NOWAIT를 지원하지 않으면 자동으로 LockMode.UPGRADE로 전환된다.
  • LockMode.FORCE - 객체에 버전을 DB에서 증가시키도록 강제한다.
  • LockMode.WRITE - 하이버네이트가 현재 트랜잭션에 레코드를 추가했을 때 자동으로 얻어온다.(사용자가 명시적으로 애플리케이션에서 사용할 일 없음.)
  • load()와 get()은 기본으로 LockMode.NONE을 사용한다.
  • Detached 상태의 객체를 reattach 할 때 LockMode.READ 를 유용하게 사용할 수 있다. 자동으로 reattach까지 해주니까.(하이버네이트의 lock()메소드만 reattch까지 해주지, JP의 lock()메소드는 reattch해주지 않느다.)

reattche를 할 때 반드시 lock() 메소드를 사용해야 하는 것은 아니다. 이전에도 살펴봤듯이 Session에 update() 메소드를 사용하면 Transiecnt 상태의 객체가 Persistent 상태가 된다. lock(LockMode.READ)는 Persistent 상태로 사용하려는 객체의 데이터들이 이전에 로딩된 그 상태 그대로 인지, 혹시 다른 트랜잭션에 의해 데이터들이 변경되지는 않았는지 확인하기 위한 용도다. 그렇게 확인을 함과 동시에 덤으로 Persistent 상태로 전환(reattach)시켜 주는 것이다. 즉, Transient 상태의 객체를 lock() 메소드의 인자로 넘겨줄 수 있다는 것인데, 이것은 하이버네이트에서만 할 수 있다. JP에서는 이미 Persistent 상태인 객체한테만 lock()을 호출할 수 있다.

Item item = ... ; 
Bid bid = new Bid();
item.addBid(bid);
...
Transaction tx = session.beginTransaction();
session.lock(item, LockMode.READ);
tx.commit();

Forcing a version increment

  • 하이버네이트가 개발자가 수정한 내용을 버전을 올려야 하는 변경사항인지 모를 수가 있다. 이럴 때 명시적으로 버전을 올리라고 알려줘야 한다.
Session session = getSessionFactory().openSession(); 
Transaction tx = session.beginTransaction();

User u = (User) session.get(User.class, 123);
u.getDefaultBillingDetails().setOwner("John Doe");

tx.commit();
session.close();
  • 하이버네이트는 객체와 직접적으로 닿아있는 값의 변화만을 알지 한 단계 걸친 변화는 해당 객체의 수정사항으로 인식하지 않는다.
  • 위의 코드에서 BillingDetail 객체만 버전을 올리게 된다. 하지만 개발자는 정보를 수정한 BillingDetail(aggregate)을 가지고 있는 User(root object) 역시 버전을 올리고 싶어 할 수 있다.
Session session = getSessionFactory().openSession(); 
Transaction tx = session.beginTransaction();

User u = (User) session.get(User.class, 123);
session.lock(u, LockMode.FORCE);
u.getDefaultBillingDetails().setOwner("John Doe");

tx.commit();
session.close();
  • 이렇게 하면 현재 User 객체에 대응하는 레코드를 가지고 작업하고 있는 모든 Unit of work들이 해당 객체의 버전이 올라갔다고 인식한다.
  • 심지어 아무런 변경을 가하지 않았더라도 해당 Root 객체의 버전은 올라간다.

공부할 것

  • 사용하는 DB에 따라 다른 결과가 나올 수 있다. Select ... FOR UPDATE NOWAIT 문을 지원하느냐 안 하느냐에 따라 LockMode.UPGRADE 와 LockMode.UPGRADE_NOWAIT의 결과가 LockMode.READ 와 같게 나올 수도 있다.
  • 결국 원하는 Isolation level을 정하는 것이 중요하고, repeatable read를 보장하려면 LockMode.UPGRADE 또는 LockMode.UPGRADE_NOWAIT를 사용하여 pessimisitc locking하면된다.
  • LockMode.READ는 DB에서 데이터를 읽어와서 버전을 확인한다. isolation level을 미리 올려두는 것이 아니라, optimistic 한 방법으로 DB에 쓰기 직전에 확인하기 위한 용도라고 생각된다.
  • 자동 버전 증가는 오직 엔티티가 직접적으로 물고 있는 속성, 콜렉션 자체의 변화만 인식한다. 객체 맵의 루트를 올려야 한다면, 해당 루트 객체를 가져올 때 LockMode.FORCE를 사용하며 이 녀석은 isolation level과 별 상관이 없어 보이지만, 해당 엔티티를 사용하는 트랜잭션들의 isolation level을 repeatable read로 보장해야 하는 경우에 유용하게 사용할 수 있을 것 같다.
  • 결국 테스트 코드를 많이 만들어서 테스트해봐야겠다.
top

  1. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.03.30 20:47 신고 PERM. MOD/DEL REPLY

    잘 봤습니다.
    락모드에 대해서는 공부하지 않아서 좀 이해하지 못한 부분이 있는데..
    UPGRADE와 UPGRADE_NOWAIT 는 객체를 가져오는 시점에서 락을 걸어버리는 pessimitic-lock 이고 그 외의 모드들은 optimistic-lock 과 관련된 부분인가요?

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.30 22:29 PERM MOD/DEL

    네. 그렇습니다.

    해당 row에 접근할 때 롹을 하는 pessimistic lock을 사용하는 것이 UPGRADE와 UPGRADE_NOWAIT이구요.

    나머지 중에서 NONE은 optimistic한 동시성 처리를 하지 않겠다는 것입니다. 이 말은 다시 말하자면, first-win이 아니라 last-win 전략을 사용하겠다는 것이고, 사용자에게 불편함을 끼칠 우려가 많은 전략입니다.

    그래서 READ를 사용해서 데이터의 변경사항을 DB에 반영하기 전에 혹시 그 변경사항과 관련된 객체들에 대응하는 레코드가 이미 다른 트랜잭션에 의해 변경되지 않았는지 확인해서 first-win 전략을 사용할 수 있습니다.(automatic optimistic locking)

    그런데, READ와 같이 데이터를 먼저 쓰는 시점에 우선권을 주는것이 아니라 데이터에 먼저 접근하는 트랜잭션에게 우선권을 주는 pessimistic locking을 하려면 UPGRADE와 UPGRADE_NOWIAT을 사용할 수 있는 것 입니다. 그런데 이 두 개의 롹 모드는 사용하는 DB에서 SELECT ... FOR UPDATE를 지원할 때만 그렇게 동작이 되고, pessimistic locking을 지원하지 않는 DB에다가 대고 위와 같은 락모드를 설정하면 결국 READ와 동일하게 동작합니다.

    따라서 락모드를 설정하는 두 가지 방법이 있는데, optimistic locking을 할 때는 session.lock()을 사용하고, pessimistic locking을 사용할 때는 session.load()나 get()을 할 때 롹모드를 주는 것이 직관적이고 이해하기 쉬운 코드가 될 것으로 생각합니다.

Write a comment.


낙천적인 동시접근 제어

Hibernate/Chapter 10 : 2008.03.28 19:30


특징

  • 모든 게 다 잘 될거라고 가정을 하는 접근법이다.
  • unit of work의 마지막에 데이터를 쓰려고 할 때 에러를 발생시킨다.

낙천적인 전략 이해하기

사용자 삽입 이미지

  • 두 트랜잭션 모두 read commited는 기본이니까 dirty read는 허용하지 않는다.
  • 하지만 non repeatable read는 가능하다. 그리고 둘 중 하나의 update가 분실 될 수도 있다.
  • lost updat를 처리할 수 있는 세 가지 방법
    1. Last commit wins - 마지막에 커밋하는 녀석이 앞선 변경 사항을 덮어쓴다. 에러 메시지 없다.
    2. First commit wins - 두 번째로 같은 데이터에 커밋하는 녀석이 있을 때 예외를 던진다. 사용자는 새로운 컨버세이션을 시작여 새로운 데이터를 가져와서 다시 작업해야 한다.
    3. 충돌하는 업데이트 병합하기 - 두 번째로 같은 데이터에 커밋하려는 녀석이 있을 때 예외를 던지고 사용자가 선택적으로 무조건 덮어 쓸 수 도 있고 다시 시작할 수도 있도록 한다.
  • optimistic concurrency control이 없거나 기본값인 상태에서는 무조건 last commit wins 전략으로 동작한다. 비추다.
  • 하이버네이트와 JP는 automatic optimistic locking을 사용하여 first commit wins 전략을 사용하게 해준다.
  • Merge conflicting changes가 가장 사용자 입장에서 좋은데, 이런 창을 띄우고 메시지를 출력하는 것은 개발자의 몫이다.

하이버네이트에서 Versioning 하기

  • 각각의 Entity들은 version을 가지고 있고 숫자나 타입스탬프로 표현할 수 있다.
  • 하이버네이트는 이 version을 Entity가 수정될 때마다 버전을 증가 시키고 만약 충돌이 발견되면 예외를 던진다.
  • 이 version 속성을 Entity에 추가해준다.
public class Item {
...
private int version;
...
}
  • XML에서 설정할 때 이 속성에 대한 맵핑은 반드시 id 속성 설정 바로 다음에 위치해야 한다.
  • 세터를 사용하지 말고 필드에 직접 쓰도록 설정하고 세터를 만들어 두지 않는게 좋다. 하이버만 쓸 수 있도록...
public class Item {
...
private Date lastUpdated;
...
}
  • Timestamp를 사용할 수도 있는데 이건 약간 덜 안전하다. 비추한다.

비추하는 이유

Clustered 환경에서 JVM으로부터 현재 시간을 가져오는 건 위험하다. jvm이 두 개니까 둘이 같을 수도 있겠지. 한 개에서 가져오면 같을 일이.. 없겠지만, 어쨋든 그래서 타입스탬프를 DB에서 가져오도록 설정할 수 있다. source="db"라고 <timestamp> 맵핑에 추가하면 된다. 그런데 이것도 DB를 매번 다녀오니까 추가비용이 발생하고 하이버네이트의 모든 SQL Dialect가 이걸 지원하는 것도 아니다.

자동 버전 관리


자동 버전 관리 동작 원리

두 개의 트랜잭션이 같은 데이터에 가져온다. 이때 버전 넘버를 확인한다. 1이라고 하자. 그뒤에 Update문이 실행 될 때 다시 버전 넘버를 가져와서 확인한다. 맨 처음에 가져온 넘버와 같으면 커밋하고 버전 넘버를 증가시킨다. 버전 넘버가 다르면 누군가 데이터를 변경한 것이기 때문에 현재 트랜잭션은 예전 데이터를 가지고 작업하고 있는 것이다. 그러면 Update문을 날리지 않는다. 그럼 SQL의 결과 수정된 레코드의 수가 0이다. 이 숫자가 0이면 하이버는 StaleObjectStateException을 던진다. 이 예외를 잡아서 화면에 에러 메시지 보여주고 사용자가 컨버세이션을 다시 시작하도록 할 수 있다.

  • 언제 Entity의 버전을 올리는가? Entity 객체가 가지고 있는 속성들이 dirty 할 때에만 올린다.

버전 넘버나 Timestamp 없이 버전 관리하기

  • 레거시 DB나 기존의 자바 클래스를 사용하고 있어서 버전 컬럼을 추가할 수 없어도, 하이버네이트는 자동 버전 관리를 할 수 있다.
  • 단, 객체를 가져오고 수정하는 Persistent Context(Session)가 같아야 한다. 따라서 Detached 객체를 가지고 Conversation을 구현할 때에는 자동 버전 관리가 불가능하다. 그 때는 버전 넘버나 Timestamp가 필요하다.
  • 버전 컬럼 대신에 모든 컬럼을 비교한다. optimistic-lock="dirty" 라고 설정하면 dirty 상태의 속성만 비교한다. 이때에는 update문을 dirty한 컬럼으로만 생성해야 하니까 dynamicUpdate를 true로 설정해야 한다.

Java Persistent 사용하여 버전 관리하기

  • Java Persistent는 동시 접근을 Versioning을 사용하여 낙천적으로 관리한다고 가정한다.
@Entity
public class Item {
...
@Version
@Column(name = "OBJ_VERSION")
private int version;
...
}
  • JPA는 버전 속성 없이 optimistic versioning을 못하니까 하이버 애노테이션을 사용해야 한다.
@Entity
@org.hibernate.annotations.Entity(
optimisticLock = org.hibernate.annotations.OptimisticLockType.ALL
)
public class Item {
...
}
  • 락을 OptimisticLockType.DIRTY로 설정할 수 있다. 그럴 때는 dynamicUpdate 속성의 값도 true로 설정해야 줘야 한다.
top

  1. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.03.31 23:54 신고 PERM. MOD/DEL REPLY

    저도 빨리 애노테이션으로 작업하고 싶어요~

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.04.01 10:48 PERM MOD/DEL

    네.ㅋㅋ 애노테이션이 편해요.

Write a comment.


Optimistic concurrency control

DB/개념 : 2007.04.25 15:56


참조 : Wikipedia-Optimistic concurrency control
optimistic concurrency control, (OCC) is a concurrency control method used in relational databases without using locking. It is commonly referred to as optimistic locking, a misnomer.Optimistic concurrency control
Non-lock concurrency control 방법들 중에 하나로 Locking을 사용하지 않고 동시성 제어를 하는 방법을 Optimistic concurrency control 또는 Optimistic locking이라고 합니다.

트랜잭션 충돌이 거의 발생하지 않는다는 가정하에 다음의 절차로 트랜잭션을 처리합니다.

읽기: 클라이언트가 데이타베이스로 부터 값을 읽어서 private sandox 나 cache에 저장하고 클라이언트는 그것을 가지고 작업을 합니다.

검증하기: 클라이언트가 샌드박스나 케쉬에서 편집을 끝내면 그것들을 데이타 베이스에 넣기 전에 검증 작업을 거칩니다.

- backward validation schemes 에 커밋된 트랜잭션이 존재하거나
- forward validation schemes 에 현재 실행중인 트랜잭션이 존재하면

출동이 발생합니다. 충돌이 발생하면 충돌을 풀기 위한 알고리즘이 사용되거나(주로 사용자에 의해 변경된 부분을 최소화 시킵니다.)[각주:1] 전체 트랜잭션을 취소(이때는 사용자가 변경한 부분이 모두 날리게 되니까 그다지 좋은 방법은 아닙니다.)시킵니다.

쓰기: 만약 충돌이 발생하지 않았다면 커밋합니다.

충돌이 자주 발생하지 않으면 이 방법으로 다른 방법들(non-lock concurrency control) 보다 많은 작업을 처리할 수 있지만, 충돌이 자주 발생하면 성능이 떨어지기 때문에 충돌이 자주 발생할 때 효율이 좋은 다른 방법을 사용합니다.

사용한 곳
  • MediaWiki's edit pages use OCC. The conflict resolution algorithm is described here.
  • Bugzilla uses OCC; conflicts are called "mid-air collisions". [1]
  • The Ruby on Rails framework has an API for OCC. [2]
  • Most revision control systems support the "merge" model for concurrency, which is OCC.
  1. 원문에는 ideally by minimizing the number of changes made by the user 이렇게 적혀 있는데 변경 되는 사항들을 최소화 시킨다는 것이 어떤 건지 모르겠네요. queue로 변경 작업을 하는 트랜잭션을 관리하여 한번에 하나씩 처리하게 한다는 것인가;;?? [본문으로]

'DB > 개념' 카테고리의 다른 글

Multiversion concurrency control  (0) 2007.04.25
Timestamp-based concurrency control  (0) 2007.04.25
Optimistic concurrency control  (2) 2007.04.25
top

  1. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.03.11 01:38 신고 PERM. MOD/DEL REPLY

    낙관적 잠금 기능에서 동시 접근및 수정일 때 나중에 가해진 수정은 일단 update 쿼리는 수행되는데
    update 쿼리수행후 리턴받는 적용행 수를 기준으로 0이냐 1이냐를 따져서 0이면 예외를 발생시키는 걸까요? 적용된행이 0이라는 뜻 자체가 버전이 이미 올라갔으며 따라서 적용된 행이 없으므로 이걸 기준으로 판단되지 싶네요

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.11 09:36 PERM MOD/DEL

    글쎄요. 아직 하이버를 거기까지 보지 못해서요;;

Write a comment.