LINQ and Functional Programming

SQL Server 2008 관련된 동영상을 보다가 Entity Data Platform 이란거에 대해서 좀 봤고 LINQ라는 것에 대해서 좀 봤고, 최근에 알게된 Erlang 에도 생각이 미치더니만, 괜한 공상이 머릿속을 흔든다.

앞으로도 DB의 중요성은 절대 줄어들지 않을 것 같으므로 정확하고 효율적인 모델링, 능숙한 SQL사용, 성능과 저장공간을 동시에 고려한 최적의 인덱스 작성 능력은 언제나 요구되는 능력일 것이다. DB를 기반으로 한 솔루션에서 대부분의 병목은 DB에서 발생하기 때문이다. 그런데 인터넷 세상이 되고 비즈니스에서 속도가 강조되면서 개발도 함께 빨라져야할 필요가 생기고 있다. 더불어 유지보수의 용이함도 꼭 필요하다. 이런 측면에서 객체 모델의 도입이 필요하다. 하지만  OR impedence 를 해결하는게 쉬운 일은 아니며, 이를 위한 OR매핑 솔루션의 사용도 결코 쉽지 않다. 만들기는 더더욱 어렵다.

SQL2008 white paper 를 읽다보니 개발측면에서 흥미로운게 눈에 띄었는데, ADO, Visual Studio 와 함께 결합한 LINQ라는 놈이다. Language Integrated Query 라는건데, DB에 SQL로 질의를 하는게 아니라(테이블 데이터를 내놔라! 하는 기존방식) 고객 엔티티를 내놔라! 하고 요청하고 그 결과가 DataSet이 아니라 .NET 객체로 반환되는 것이다. 아... 이건 사실상의 OR매퍼가 아닌가? 다음과 같은 syntax로 사용한다.

public partial class DataDemos_1_HelloWorld : System.Web.UI.
{
    protected void Page_Load(object sender, EventArgs e)
    {
        AdventureWorks db = new AdventureWorks();

        var query = from person in db.SalesPeople
                         where person.HireData > new DataTime(2002,1,1)
                          select person;

        DataList1.DataSource = query;
        DataList1.DataBind();
    }
}

코드를 보자. 저거 ASP.NET 코드다. 

근데 AdventureWorks 라니? 그렇다.. SQL2005 의 데모 DB인 AdventureWorks를 객체로 이해하고 접근하고 있다. db.SalesPeople 는 저 DB에 있는 스키마일까? person 은 어떤 테이블하고 매핑되어 있는 것일까? 글고 원본 그림을 보면 "Results are .NET objects, strongly typed, support data binding" 이라고 되어 있다. 음.. 별도의 비즈니스 객체 레이어를 생성해줘야지 거기다가 메서드를 추가할텐데...  혹시 매핑관계를 별도로 다 정의해줘야 하는걸까? 그 노가다도 상당히 골때릴텐데... 게다가 저 놈이 결국 DB에 날리는건 SQL일텐데 이 놈의 성능은 어떨까? 객체를 Persist 할 때 만들어 내는 SQL은 객체의 프라퍼티중 변경된 넘들만 들어가게 만들어 줄까? DBA입장에서 보기엔 DB관리가 점점 더 힘들어지는 상황인 것 같다.

관련 링크 : http://link.allblog.net/4060901/http://pobeez.egloos.com/218530
관련 링크 : http://blogs.msdn.com/charlie/archive/2007/01/26/anders-hejlsberg-on-linq-and-functional-programming.aspx (동영상 꼭 볼것!)

동영상을 보면 대충 뭘 하려는지 짐작이 된다. 데이터를 다루는 시스템을 예로 들어보자. 결국 다른 소스에서 온 서로 다른 데이터들을 이리저리 엮고, 계산해서 보여주고 인풋을 계산해서 보여주는거 아니던가 말이다. LINQ가 하려는건 언어에 데이터를 다루는 기능을 통합하되, 그걸 좀 더 추상적인 레벨에서 수행할 수 있게 하는거다. 음.. 간단하게 설명하자면 C#같은 언어에 DB의 쿼리 엔진같은넘이 들어간다고 보면 되겠다. 동영상에 함수형 프로그래밍이니 지연된 실행이니 타입 추론이니 어쩌구 나오는데... 설명 가만 들어보면 SQL을 DB가 받아서 parse tree만들고 그러면서 타입체크하고 실행 계획 만들면서 CPU갯수 체크해서 병렬로 돌릴 수 있는거 돌리고... 그러는거랑 LINQ에서 추구하는게 대단히 흡사하다. 얘네들은 언어레벨에서 이걸 하려고 하는거고, 이렇게 하기 위한 최고의 방법이 함수형 언어 패러다임의 도입이고, 이것의 밑바탕은 선형 대수이고 이게 되면 람다식을 해석해서 만든 expression tree를 보고 CPU남으면 병렬로 돌릴지 결정하는거도 쉽게 쉽게 할 수 있다는 말이 되겠다.

LINQ를 자유자재로 쓰게 되면 XML하고 텍스트, DB데이터를 필터링해서 읽어온다음 join 해서 group by 한 다음에 결과를 어디로 보내는 작업을 DB에서 쿼리 만들듯이 할 수 있게 될거다. 중간에 Hashtable에 넣고 Vector에 넣고 이러는거 하나도 없다. 알아서 한다는게 동영상의 내용으로 보인다. 뭐, DB에서 현재 하고 있는거랑 똑같자나? ETL툴들도 여러 데이터 소스에서 읽어와서 join , group by 하는거 다 된다. 특정한 모듈, 이를테면 DW에서 많이 쓰는 lookup 같은것도 CPU 여러개 쓰면서 잘 돈다. 데이터를 핸들링하는 과정이 단위 기능으로 쪼개졌기 때문이다.

자 그러면 이런 문제를 자연스럽게 예상할 수 있다. expression tree를 만드는게 얼마나 효율적인 것이냐. DB에서 실행 계획 만드는게 의외로 CPU많이 쓰는 작업임을 생각해보면 이런 문제도 분명히 생길거다. 글고 실행계획 재사용같은게 되려나? 앗. 프로그램이니까 컴파일이 되는구나-_-; 실행계획 재사용에 해당하는 문제는 없겠다. ㅋㅋㅋ 근데 만들어낸 expression tree가 진짜로 최적일거냐 하는 문제는 여전히 예상이 된다.  두개의 XML소스와 세개의 텍트스 소스와 하나의 DB에서 데이터를 읽어서 join을 한다고 치면 뭘 먼저 읽고 어떤 순서로 join 할지는 상황에 따라 최적값이 다를텐데. 뭐, 이건 DB 튜닝을 하다보면 맨날 만나는 문제지만. 그럴때는 지가 알아서 컴파일된 코드를 바꿀까???? ㅋㅋㅋ

적으면서 생각을 정리하다보니 비전은 원대하나 실무에서 검증되기엔 많은 난관이 있어보인다는 생각이 든다. 게다가... 객체도 잘 모르는 대다수의 개발자들이 함수형 프로그래밍도 공부해야 한다는 어려움도 예상이 되는구먼. 절차지향, 집합지향, 객체지향, AOP, 함수형 등등 뭐가 이리 많단 말이냐. ㅎㅎㅎㅎ


@제가 FP나 LINQ에는 전혀 경험이 없으니 위의 동영상을 깊이있게 이해하지 못하거나 오해하고 있는 부분이 있을 수 있습니다. 추가하실 말씀이 있으시면 덧글로 가르침을 주시면 감사하겠습니다.

Posted by maceo

06 7, 2007 17:01 06 7, 2007 17:01
, , , , ,
Response
No Trackback , a comment
RSS :
http://merritt.co.kr/tt/rss/response/102

coalesce 함수의 특이한 동작

박노철
maceo.park@gmail.com
http://merritt.co.kr

SQL Server 를 보면 isnull 과 coalesce 가 있다. isnull 은 다 아는거고 coalesce 는 사람들이 잘 모르는데, 넘겨받은 n개의 파라미터중 null 이 아닌 첫번째 값을 리턴하는 함수다. (사족인데, isnull 은 ANSI 표준이 아니고 coalesce 는 ANSI 표준이다. Joe Celko 책에는 isnull 의 자리에 coalesce 을 사용하고 있다. )

우찌됐던간에, coalesce 는 함수 자체의 특성상 다음과 같은 상황에 유용하게 쓰일 수 있다.

1. 카테고리별로 설정된 어떤 값을 읽어온다. 만약 그게 없으면...
2. 상품별로 설정된 어떤 값을 읽어온다. 그게 없으면...
3. 판매자별로 설정된 어떤 값을 읽는다. 그래도 없으면
4. 디폴트값을 리턴한다.

어떤 제약사항이 우선순위를 가지고 적용되는 경우라 할 수 있겠다. coalesce 를 써서 위의 로직을 대충 적어보면 다음과 같다.

select
coalesce(select val from cate where cate_id=@id,
select val from prd where prd_id=@id,
select val from seller where seller_id=@id,
10)

(cate_id, prd_id, seller_id 는 PK라 가정)


물론 isnull 로도 구현할 수 있다. isnull 을 중첩해서 쓰면 되니까.

select
isnull(select top 1 val from cate where cate_id=@id,
isnull(select top 1 val from prd where prd_id=@iid,
isnull(select top 1 val from seller where seller_id=@id,10)
)
)


하지만 코드는 coalesce 쪽이 더 깔끔해보인다.

그리고 이제사 본론인데, coalesce 가 실제 성능상 더 좋은 효율을 보인다. 만약 cate 에서 NULL 이 아닌 val 값이 리턴되었다면 "상식적으로" prd 나 seller 테이블을 액세스할 필요가 없다. 그런데 isnull 은 그런 상식과 반대로 행동한다. 일단 무조건 cate, prd, seller 를 모두 액세스한 다음, isnull 연산을 시작한다. 반면 coalesce 는 첫번째 파라미터가 NULL 이 아닌 값이 리턴이 되면 그 다음 파라미터들은 evaluation 하지 않고 처리를 끝낸다. 따라서 cate 에서 NULL 아닌 값이 리턴되었다면 prd, seller 테이블은 액세스하지 않는다.

믿기지 않겠지만 사실이다. ^^;

확인해보기 위해서는 set statistics io on 을 해서 나오는 scan count 를 보면 된다. isnull 을 쓰면 세개 테이블 모두 scan 카운트가 1이지만 coalesce 는 cate 만 scan count 가 1이고 나머지 테이블의 scan count 는 0 이다.

coalesce 를 쓸 때 주의할 점이 하나 있다. 위의 사례는 coalesce 안에 서브쿼리를 사용한 것이다. SQL서버 2000, 2005 모두 coalesce 안에 서브쿼리가 들어가면 실행계획이 엉망이 되는 버그? 가 있다. 아마 위의 쿼리를 그냥 실행하면 cate, prd, seller 테이블을 두번씩 액세스하는 실행계획이 세워질 것이다. (참조 : http://www.aspfaq.com/show.asp?id=2532 )아까전에는 scan count 가 1 이라고 했지만, 사실은 잘못된 실행계획때문에 scan count가 2이다. 이를 방지하기 위해서는 쿼리를 다음과 같이 만들어야 한다.

select
coalesce(select top 1 min(val) from cate where cate_id=@id,
select top 1 min(val) from prd where prd_id=@id,
select top 1 min(val) from seller where seller_id=@id,
10)


cate_id, prd_id, seller_id 가 PK 이지만 일부러 min 을 씌워서 값이 없을때도 확실하게 NULL 을 리턴하도록 했고, top 1 을 해서 한개의 값만 가져오도록 했다 .이렇게 하면 정확하게 우리가 원하느대로 scan count 는 1이 나오면서 불필요한 테이블을 액세스하지 않는다. coalesce 와 서브쿼리의 실행계획에 대한 문제, 기타 isnull 과 coalesce 에 관해서는 다음 문서를 참조한다.

http://www.examnotes.net/archive79-2002-8-55509.html 에서 Umachandar Jayachandran 의 리플라이

http://toponewithties.blogspot.com/2004/08/differences-between-coalesce-and.html

여기를 시작으로 블로그 트랙백을 따라가다 보면 엄청많은 문서들이 있다.

Posted by maceo

03 20, 2006 09:40 03 20, 2006 09:40
, , ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/36

신비의 양쪽 like 검색? -_-

박노철
maceo.park@gmail.com
http://merritt.co.kr

1)
declare @val varchar(10)
set @val = 'TEST'
select * from table1 where col1 like '%' + @val + '%'


2)
select * from table1 where col1 like '%TEST%'

(table1.col1 에 nonclustered index 가 잡혀 있다고 가정)


자, SQL 서버 2000에서 1) 과 2) 의 실행계획은 어떨까? 일반적으로 보면 둘다 index seek 을 하지 못한다고 알려져 있다. 그런데 막상 실제로 보면 안그렇다. 1) 의 경우 '%' + @val + '%' 을 서버가 희한하게 변경한다. 실행계획을 떠보면, col1 >= LikeRangeStart(col1) and col1 < LikeRangeEnd(col1), LikeRangeInfo 이렇게 세개의 Expression 으로 변환한 후, 이것을 Compute Scalar 연산을 한 뒤에 col1 과 inner join 을 한다. 황당하기 그지없다. 2)는 예상한대로 index scan 을 한다.

자 그럼 성능은 어떨까? 1)이 index seek 을 하니까 좋지 않을까? 아니올씨다. 이다. nonclustered index 를 범위검색을 하기 때문에 성능이 결코 좋지 못하다. index scan이야 뭐, 테이블이 커지면 당연히 안좋을 수 밖에 없고.

그럼 이 글은 도대체 왜 적었냐고? SQL서버 옵티마이저가 괴상하게 행동하는게 신기해서 함 적어봤다. ㅎㅎㅎ



@참고로 LikeRangeStart, LikeRangeEnd, LikeRangeInfo 함수는 Books Online 에도 나오지 않으며, 구글을 해봐도 마땅한 정보가 나오지 않는다. MS에서 공개하지 않고 있는 함수의 하나인 것 같다.

Posted by maceo

01 4, 2006 22:05 01 4, 2006 22:05
,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/18


블로그 이미지

가늘어도 긴놈이 장땡

- maceo

Archives

Authors

  1. maceo

Calendar

«   3 2010   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      

Site Stats

Total hits:
170288
Today:
32
Yesterday:
47