개발자가 할줄은 알지만 은근히 모르는 DB개발과 관련된 글을 쓰려고 합니다. 저도 SP를 실제 프로젝트에 사용한 것이 얼마 안되었고 프로그램 개발에 더 많은 시간을 투자하였습니다. 그래서인지 신선한?! DB개발을 하니 신입때 첫 프레임워크를 사용하는 느낌입니다.

구글에서 DeadLock과 관련된 글들을 보고 제가 이해한 것을 기반으로 설명을 해보려고 합니다. 틀린 부분이 있다면 댓글을 달아주시면 좋습니다. (댓글이 없어서 사실 아무말이나 좋습니다 ㅎㅎ)

먼저 DeadLock이기 때문에 트랜잭션과 Lock 이야기로 시작해보겠습니다.
이전 글에서 이야기 했듯이 트랜잭션(IsolationLevel)에 대해서 일반적으로 ReadUncommitted로 설정을 하고 사용할 것이라고 하였습니다. 그렇게 생각하는 이유는 ReadCommitted나 RepeatableRead, Serializable 등등은 만족할만한 성능을 얻기가 힘들며 Lock을 잠그는 시간이 길어지면서 DeadLock이 발생할 확률도 높습니다.

당연한 사실이지만 트랜잭션이 발생한 상태에서 Lock도 발생하는 것입니다. 트랜잭션이 없다면 Lock도 없습니다. 그렇다고 트랜잭션이 있다고 Lock이 꼭 있는건 아닙니다. 바로 트랜잭션 IsolationLevel 옵션을 ReadUncommitted를 사용(일반적으로 Select구문일 경우)할 때입니다. ReadUncommitted는 그 이름처럼 다른 트랜잭션에서 커밋이 되기 전의 데이터를 읽을 수 있습니다. Lock없이 읽기 때문에 성능적인 측면이나 DeadLock이 발생하는 것을 방지하려는 의도로 사용되는 것입니다. 다만 커밋되기 전의 데이터 읽게 되므로 데이터의 무결성이 깨질 수 있습니다.
=> 위의 Lock이 없다는 말은 잘못된 표현이네요. 아무리 짧은 SQL 구문이라도 Lock이 발생합니다. 저 말의 의미는 실행시간이 매우 짧은 Select 구문일 경우 트랜잭션 내에서 ReadUncommitted일 경우 실행 후 Lock이 해제된다는 사실에 대한 강조하려다 보니 잘못된 표현을 한 것으로 보입니다. (2017-08-20)

하나 더 살펴보아야 할 IsolationLevel은 Snapshot입니다. 아래처럼 DB에 설정이 필요합니다.

-- 1. Snapshot 허용
ALTER DATABASE [DB명]
SET ALLOW_SNAPSHOT_ISOLATION ON

-- 2. ReadCommitted 옵션을 사용할 경우 Snapshot옵션으로 처리
ALTER DATABASE [DB명]
SET READ_COMMITTED_SNAPSHOT ON

=> 1번이 DB에 Snapshot옵션을 설정하는 명령어입니다. 2번 명령어는 SQL Server의 IsolationLevel ReadCommitted기본값이므로 해당 옵션을 사용할 때 Snapshot으로 처리하기 위한 명령어입니다. (확실한 것은 아니지만 2번으로 명령어를 사용하여 ReadCommitted로 테스트할 때와 1번 명령어만 사용하여 Snpashot옵션을 주어 처리할 때의 결과가 다른 적이 있어서 2번 명령어는 사용하지 않고 명시적으로 Snapshot옵션을 주는 것이 바람직해보입니다.)

그럼 Shapshot옵션을 주면 트랜잭션 사이의 격리성을 어떻게 관리하게 되는 것일까요? 트랜잭션이 시작되면 별도 DB(temp)에 트랜잭션 시퀀스 번호를 관리하여 트랜잭션 사이에 경합이 발생하면 경합이 발생한 트랜잭션 중 하나를 롤백(낙관적 동시성 모델)시켜서 데이터의 무결성을 보장하고 DeadLock을 방지해줍니다. 자세한 내용은 아래 MSDN을 참고하시면 됩니다. 여기서 중요한 것은 경합이 발생할 때 트랜잭션을 취소시키기 때문에 실패하는 트랜잭션이 발생하지만 동시성(성능)은 높은 옵션이라고 말할 수 있습니다. 다만 동시성이 높아지면 높아질수록 성공률은 낮아지게 되는 문제점이 있습니다.

https://msdn.microsoft.com/ko-kr/library/tcbchxcb(v=vs.110).aspx

그럼 정리를 해보면 트랜잭션 IsolationLevel중 성능을 고려하면 아래 2가지 중 하나를 선택할 수 밖에 없습니다.
1. ReadUncommitted => 단점은 더티 데이터로 인하여 데이터 무결성이 깨질 수 있습니다.
2. Snapshot => 동시성이 높아지면 성공률이 낮아집니다.

기본적으로는 ReadUncommitted를 사용하고 복잡한 트랜잭션에 대한 Lock을 관리하여 데이터의 무결성을 유지해야 합니다. Snapshot옵션은 동시성(DB)이 그렇게 높지 않은 시스템에서 안전하게 운영을 하고 싶을 경우에 사용하면 좋지 않을까 싶습니다.

DB Lock의 종류는 아래와 같습니다.
1. X-Lock(Exclusive, 배타) : Insert, Update, Delete와 같이 데이터 수정하는 작업을 말하며 같은 리소스에 대해서 동시에 실행될 수 없습니다. 즉 하나의 트랜잭션에서 X-Lock으로 잠금을 설정하면 다른 트랜잭션에서는 어떠한 잠금도 설정할 수 없습니다.
2. S-Lock(Shared, 공유) : 일반적으로 Select로 대표되며 데이터 읽기 작업을 합니다. 하나의 트랜잭션에서 S-Lock 잠금을 설정하면 다른 트랜잭션에서도 U-Lock, S-Lock 잠금이 가능합니다.
3. U-Lock(Update, 업데이트) : 설명을 짭게 하기가 힘들지만 U-Lock 잠금이 걸려 있어도 S-Lock만은 걸 수 있다 정도로 알면 될것 같습니다.
(이외에도 I-Lock, Schema, Bulk Update, Key-Range가 있지만 너무 많은 정보는 이해하는데 장애물이 되므로 패스하는 것이 좋습니다.)

일단은 이정도만 대략적으로 이해한 상태에서 간단한 예제를 통해서 DeadLock 상황을 만들어보고 원인과 해결 방법을 통해서 이해력을 높이면 충분히 DB도 아는 괜찮은 개발자라고 할 수 있을 것 같습니다. 저는 이미 괜찮은 개발자! ㅋㅋㅋ

참고 : Lock과 관련되어 상세하게 정리되어 있는 사이트입니다.
http://www.dbguide.net/db.db?cmd=view&boardUid=148215&boardConfigUid=9&categoryUid=216&boardIdx=138&boardStep=1


Posted by resisa
,

또?! 오랜만입니다 ㅎㅎ 지인이 애드센스 등록을 하셔서 저도 시도를 해보았더니 콘텐츠 부족으로 실패하여 이렇게 새로운 글을 쓰고 있습니다.

어떤 내용을 쓸까 고민을 하다가 블로그를 정리하면서 예전에 썼던 글 중에 그 당시 궁금했던 내용에 대한 답변을 해보고자 합니다.
(벌써 7년이 지났다니 시간이 참 빠릅니다;;)

TransactionScope 그리고 Stored Procedure (2010. 07. 25)
출처: http://resisa.tistory.com/110 [심플하게~]

(중략)...

1. 한 가지 의문점은 SP안에서는 트랜잭션의 Scope 범위(TransactionScopeOption) 지정하는 부분은 없어보이지만 IsolationLevel 대해서는 지정을   있습니다또한 TransactionScope에서도 IsolationLevel 대해서 지정할  있습니다그래서 IsolationLevel 따른 LTM SQLTransaction 관계가 어떻게 될지도 살짝 궁금해집니다. 하지만 실질적으로 이러한 것을 프로젝트에서 정확하게 알고 사용하는 개발자가 얼마나 될지도 의문이며 IsolationLevel 트랜잭션 사이의 잠금(lock) 대한 것이기 때문에 2. 성능보다는 안전성을 중요시하는 우리 나라의 대부분의 프로젝트에서는 ReadCommitted 사용하는 것이 개발자의 정신건강에 이로워 보입니다. ^^

예전에 썼던 글에 은근히 잘못된 내용이 있습니다;;  이유는 이해부족 입니다;;
저는 새로운 기술을 습득하는 과정에서 '정의'를 자신의 방식으로 표현하고 증명하는 과정을 반복합니다. '정의'와 '증명'을 잘못하면 위와 같이 잘못된 글이 나오기도 하지만 경험이 붙으면 증명하는 시간이 짧아지고 단순 실수도 줄어 이해하는 시간이 짧아지고 자신의 표현 방식으로 인하여 오랫동안 기억되는 장점이 있는 방식이라고 생각됩니다. 그럼 잘못된 부분부터 수정해보겠습니다.

=> 1번에서는 개념에 대해서 잘못 설명하고 있습니다. TransactionScopeOption은 TransactionScope클래스의 파라미터로 DB(SP)에는 없는 속성입니다.

=> 2번 대부분의 프로젝트에서 ReadCommitted로 설정할 것이라는 이유는 MSSQL의 기본 IsolationLevel이 ReadCommitted이기 때문입니다. 이 당시에는 IsolationLevel에 대한 이론적인 것만 알고 있었기 때문에 이런 결론을 냈었는데 업그레이드된?! 지금은 대부분의 프로젝트에서는 ReadUncommitted를 사용할 것이라고 생각합니다. 왜냐하면 ReadCommitted로 설정을 하면 DeadLock이 발생하는 구간이 많이 있을 수 있습니다. 그렇기 때문에 직접적으로 ReadUncommitted를 지정하지 않았더라도 이와 동일한 WITH(NOLOCK) 힌트를 많이 사용했을 것이라고 추측됩니다.

그럼 본론으로 들어가서 노란색 부분에 대한 이야기를 풀어보겠습니다.

먼저 트랜잭션을 아래와 같이 2가지 형태로 구분합니다.
1. 로컬 트랜잭션(SQL Transaction)
2. 분산 트랜잭션(DTC Transaction)

1. 로컬 트랜잭션을 세부화 하면 아래와 같이 나눌 수 있습니다.
1.1. TM(Transaction Manager)이 관여하는 SQL Transaction
1.2 TM이 관여하지 않는 SQL Transaction

개념적인 측면에서는 TM의 관여 여부로 나눌 수 있으며 개발적인 측면에서는 프로그램에서 트랜잭션을 관리하는지 DB에서 즉 SP에서 트랜잭션을 관리 여부로 나눌 수 있습니다.

다시 노란색 부분을 보면 SP에서 트랜잭션을 관리하는 부분들이 프로그램에서 트랜잭션을 관리하는 것과 합쳐졌을 경우에 트랜잭션 관리(Connection, IsolationLevel)에 대한 궁금증이였습니다.

프로그램에서 트랜잭션 관리를 하는 방법은 아래와 같습니다.
1. ADO.NET(System.Data.SqlClient.SqlConnection)를 사용하여 직접 BeginTransaction()를 호출하여 사용
2. System.Transactions.TransactionScope를 사용

트랜잭션을 관리하는 1번, 2번 방법의 공통점은 프로그램에서 트랜잭션을 관리하기 때문에 TM이 트랜잭션을  관리한다는 점입니다. 차이점이 바로 이 글의 핵심이 되는 답변으로 IsolationLevel과 관련되어 있습니다. TransactionScope에 IsolationLevel을 지정하여 사용할 때 SP의 IsolationLevel을 변경할 수 없다는 것입니다. IsolationLevel에 대한 위의 네임스페이스를 살펴보면 아래와 같습니다.

1. System.Data.SqlClient.SqlConnection - System.Data.IsolationLevel
2. System.Transactions.TransactionScope - System.Transactions.IsolationLevel

조심스럽게 예측해보면 TransactionScope의 트랜잭션은 DB 트랜잭션이라기보다는 프로그램에서 발생하는 트랜잭션(여기서는 DB 트랜잭션을 포함하는 상위의 트랜잭션의 개념으로 이해)이라고 할 수 있고 IsolationLevel은 트랜잭션 사이의 격리성이기 때문에 프로그램에서 발생하는 트랜잭션에 대한 격리성이지 DB 트랜잭션에 대한 격리성이 아니기 때문에 DB(SP) IsolationLevel을 변경할 수 없는 것입니다.

결론을 맺으면 프로그램에서 DB(SP) 트랜잭션 관리(IsolationLevel)를 하고 싶으면 SqlConnection을 사용하여 직접 트랜잭션을 발생(BeginTransaction())시켜야하며 TransactionScope을 통해서는 DB(SP) IsolationLevel까지는 관리할 수 없다는 것입니다.

Posted by resisa
,

오랜만에 글을 쓰고 있네요 ㅎㅎ 12월이 지나면 어느덧 저도 개발 10년에 접어들게 됩니다. 믿기지 않네요 ㅎㅎㅎ

최근 회사를 이직하고 이제 적응(출근적응 ㅎㅎ)도 되고 글쓰기 관련 도서를 읽으면서 문득 블로그질을 하고 싶다는 생각이 들었습니다.


신규 프로젝트에 많은 오픈 소스를 사용하다 보니 관련 글을 쓰려고 합니다. 먼저 오픈 소스를 사용하는 것에 대한 이야기를 하려고 합니다. 어떤 개발자 분이 오픈 소스 관련 대화를 하다가 '의미있는 바이너리를 만들어내야 한다.'라는 이야기를 한적이 있습니다. 이 말에 대략적인 의미는 아래와 같습니다.

1. 오픈 소스처럼 의미있는 것을 만드는 개발자가 되어야 한다.

2. 오픈 소스가 모든 것을 해결해주지는 못한다. 즉 한계가 존재하여 직접 만들 수 밖에 없다.


1번은 이상적이고 멋있는 개발입니다. 아마도 이분은 2번에 더 많은 의미를 실어서 이야기를 한다고 느껴졌습니다. 


언제부터인지 개발자를 보면 1. 기술에 관심이 많은 개발자 2. 업무에 관심이 많은 개발자로 분리가 됩니다. 교과서적인 이야기지만 기술 + 업무가 조화를 잘 이루어야 진짜 의미있는 결과물이 나옵니다. 1번이 강한 사람은 2번에 관심이 없고 2번이 강한 사람은 1번에 관심이 없습니다. 모두가 그런 것은 아니지만 제가 봐온 많은 개발자들은 편차가 심합니다. 아마도 위에 언급한 분은 기술에 관심이 많고 업무에는 별 관심이 없는 개발자입니다. 기술에만 관심을 가져도 되는 분야가 있습니다. 하지만 업무가 중요한 부분을 기술에만 관심있는 개발자가 설계를 하고 리드해가는 과정과 그 결과물을 보면서 '앙꼬없는 진빵'이라는 생각이 들었습니다. 누구를 위한 기술이고 무엇을 위한 기술인지 알수가 없었습니다. 물론 저의 생각이고 시야가 좁아 그런걸 수도 있습니다. 업무가 중요한 분야에서 업무에 대한 관심과 분석 없이는 좋은 결과물을 만들어낼 수 없다고 확신합니다.


그럼 이제 본론?!으로 들어가서 오픈 소스 선택에 대한 기준에 대해서 이야기를 해보겠습니다.

동일한 기능을 제공하는 오픈 소스들이 많이 있습니다. 

예를 들어 IoC Container만 하더라도 20여 가지가 넘습니다.

저는 아래와 같은 기준으로 오픈 소스를 선택합니다.

1. 성능

2. 기능

3. 레퍼런스 사이트

4. 문서, 커뮤니티 기타 등등


1번째로 성능입니다. 여기서 순서는 우선순위를 의미합니다. 많은 기능을 가지고 있더라도 성능이 떨어지면 제외가 된다는 의미입니다. 성능을 가장 우선순위에 놓은 이유는 성능에 관련된 문제가 발생하게 되면 사용한 오픈 소스에 전체 구조 및 문제점을 분석 해야하고 수정을 하면 수정으로 인한 부작용까지 감수해야 하는 어려움 때문입니다. 기능에 대한 단순 버그를 수정하는 것과 근본이 흔들리는 성능은 큰 차이점이 있다고 생각합니다. 그리고 의외로 기능만 보고 오픈 소스를 선택하는 경향이 있습니다. 기능이 되기 때문에 일단 도입을 해서 사용을 하다가 성능 때문에 고생을 한 경우를 몇 번 보았기 때문이기도 합니다. 성능을 고려할 때 절대적인 수치로만 고려할 부분은 아니고 상대적인 수치로 조금 성능이 낮더라도 기능이 우수한 오픈 소스를 선택하는 경우도 있습니다.

2번째는 역시 기능입니다. 원하는 기능이 되지 않으면 당연히 의미가 없습니다. 기본 기능을 살펴보고 당장은 사용하지 않더라도 확장성을 고려해서 선택합니다.

3번째로는 해당 오픈소스를 사용한 레퍼런스 사이트입니다. 예를 들어 Dapper와 같은 매퍼는 Stackoverflow에서 사용한 오픈 소스로 레퍼런스 사이트만 들어도 신뢰가 생기는 오픈소스입니다.

마지막으로는 문서, 커뮤니티, 사용성 등등 고려해야할 요소들이 있습니다. 


위의 내용을 기준으로 제가 이번에 선택한 IoC Container는 SimpleInjector입니다.

1번째 성능에 의해서 SimpleInjector와 Lightinject를 선택하였습니다. 2번째 기능에 대해서 IoC Container 본연의 기능과 추후의 확장가능한 기능인 AOP를 둘 다 제공합니다. 3번째는 잘 모르겠고 문서, 커뮤니티 등등 우열을 가리기가 쉽지 않았습니다.

작년에 LightInject를 간단하게만 사용해보았고 이번에 검토를 하면서 SimpleInjector를 사용해보았습니다. 사용해본 느낌은 LightInject는 유연한 느낌이고 SimpleInjector는 엄격한 느낌입니다. 이런 느낌의 근거는 의존성 주입이 되는 객체가 1개 또는 N개에 따라서 컬렉션(List<T>)으로 받을 수 있는지 없는지에 대한 차이 때문입니다. 전자는 받을 수 있고 후자는 예외가 발생합니다.

평상시에 저라면 유연한 것을 선택했을 확률이 높은데 컨테이너는 엄격해야 한다는 생각이 들었습니다.

이번에 검토를 하면서 이전에 사용해봤던 Spring, Unity, Windsor, MEF는 유물이 된 느낌이였습니다. 일단 성능 수치가 상대가 안되었고 요즘 아이들은 이름처럼 간단하고 가벼운 느낌이 들었습니다.


IoC Container로 프로젝트를 처음부터 해본적이 없었는데 적용을 해보면서 경험해봐야만 알 수 있는 내용과 왜 사용하였는지에 대해서도 해당 프로젝트의 1차 개발이 끝날때쯤(한3월초? ㅎㅎ) 작성해보도록 하겠습니다.

Posted by resisa
,