본문 바로가기

Development/Coding

몽고DB 의 좋은 점 몇가지

최근 웹 코딩 작업 시 데이터베이스가 필요한 경우 가능하면 mongodb 를 사용하고 있습니다.


그동안 많은 다른 프로젝트들처럼 대부분의 경우 mysql 을 중심으로 구조를 설계하고 그에 맞는 프로그램을 개발했지만 몽고에 맛을 본 후 mysql 로 작업하기가 싫더군요.


개인적으로 느낀 MySQL 의 단점과  Mongo 의 장점을 적어볼까 합니다.


** 덧글로 몇분께서 의견 및 비난을 주셨는데 맞는 말씀입니다. 오래 전 몽고 사용하며 즐거웠던 때 작성한 내용이라 몽고 편향적인 내용만 가득하죠. 몽고도 점점 좋아지고 있고 기존의 데이터베이스 제품도 좋아지고 있어 현재 정확한 내용이라고 보기 힘들 수 있습니다. 이 글의 내용은 나홀로 개발자, 회사 내에 개발자 1명인 시스템인 상황에서 작성한 내용이니 참고해주세요. (2015.12.15)


## MYSQL 에 대한 불만


* 테이블 설계(스키마) 하기 싫다.


이건 사람마다 다르지만 저도 모델 설계하는게 재미있는 경우도 있었습니다. 물론 그럴만한 프로젝트에서 그럴만한 업무를 맡은 경우에 그랬습니다. SI 시장에서 나온 후 개인 회사에 소속되어 사내 프로젝트를 진행 하는 경우 초반 설계 단계에 모델링은 재미있는 일이었고 매우 중요한 일이었습니다. 하지만 나 홀로 개발 환경에서 모델링에 시간을 너무 빼았기는 경우가 발생한다면 전체 제작의 반 이상을 차지하는 코딩 시간이 줄어들게 되더라구요.


게다가... 틀에 짜여진 환경 및 프로젝트가 아니라 요구사항이 종종 변하기까지 합니다. 물론 안 그런 프로젝트가 없겠지만요. 그러면 릴레이션 확인도 다시 해야 하고 필드도 점검해야 합니다. 좋은 툴들이 있어 그 과정이 단순화되고 자동화되긴 하지만 생각을 집중해야 함에는 틀림이 없습니다.


설계한 테이블과 웹에서 처리하는 폼의 형태가 일치하지 않는 것도 불편했습니다. 대부분의 환경에서 폼의 값을 그대로 사용할 수 없다는 것이죠. 그럴 필요가 없는 프로젝트에서도 HTML 폼의 데이터는 컨트롤러를 통해 모델을 거쳐 데이터베이스에 저장이 됩니다. 이 중간 과정을 처리하는 데에 상당한 시간이 소요되고 추가되고 삭제되는 과정에서 반드시 손을 거쳐야 하는 작업이 필요했습니다.


네, 하기 싫어요.


* 배열을 한번에 저장하지 못한다.


이건 몽고를 사용하기 전에 항상 불평하던 내용입니다. 물론 포스트그레스 같은 진짜 멋진 DB 시스템은 배열 타입이 있어서 해당 사항이 되지 않지만 제가 알기론 MySQL 은 일반적인 환경 지원하지 않고 있습니다. 


배열을 받아서 별도의 테이블에 FK 를 걸든 그저 텍스트 타입으로 저장하든 고민을 해야합니다. 이게 웃긴게 저장하고 끝이 아니라 다시 끄집어 낼 때도 별도의 코드가 필요합니다. 뻥 좀 보태서 더 짜증 내보자면 수정 작업이 필요한 경우 코드는 더 늘어납니다. 통째로 넣고 뺄 건지 부분 부분 검색하고 찾아서 갱신할 것인지 고민해야하구요. 그냥 내 맘대로 편한 대로 했다간 반드시 요구사항이 달라져 수정해야 합니다. 한번 짜 놓은 코드 다시 보기 싫은 건 저만 그런가요? 작업하기 전에 업무 담당자에게 물어봐서 확인 받고 작업하면 되지 않겠냐 하지만 대부분 이런 사항을 결정하는 사람들은 잘 모릅니다. 게다가 (경험상) 이런 고민을 들으려고 하지도 않더라구요. 그보다 이런 결정은 나중에 반드시 번복됩니다. "어 그거 이렇게 작동 되야하는데...?" 시발   


* 쿼리 짜기 싫다.


NoSQL 을 접하면서 (물론 몽고랑 레디스 뿐이지만) 쿼리 짜는 일이 없어졌습니다. SI 노동자 시절 코딩도 하고 쿼리도 짜고 서버 들어가서 세팅도 하고 그러면서 왜 DBA 는 인력에 편성해주지 않느냐는 불평불만을 하면서 구글링으로 기본적인 쿼리만 하는 수준이지만 코드를 짜면서 SQL 문장이 들어가는 게 그닥 보기 좋은 작업은 아니었습니다. MyBatis 같이 쿼리만 따로 모아두는 경우도 있지만 SI 현장 밖에서 Java 로 시스템을 구성한 적이 없어서 요즘은 어떻게 하는지 모르겠네요. 프리페어드 스테이트먼트든 아니든 스트링 문장으로 쿼리를 구성하는 게 어려웠습니다. 


필드 확인하려 다시 ERD 봐야하던가 아니면 툴 열어서 필드명 확인해야하고 모델 맵퍼가 있다면 해당되는 DAO 구성해야하고 할게 많습니다. 하지만 혼자 하긴 너무 싫어요.



이 외에도 그냥 오라클이 싫다 이런 것도 있지만 큰 이유는 이렇네요. 몽고DB 는 이 과정이 줄어들어 좋더군요.


## MongoDB 의 장점


* 스키마 프리!


와 이렇게 편할 수가... 물론 NoSQL 시스템도 모델링 과정이 필요합니다. 하지만 내가 작업하는 수준에서는 그닥 필요하지 않았어요. 그냥 바로 코딩으로 들어가서 웹에서 사용자들에게 폼 데이터 받으면 그냥 그게 끝인 경우도 많았고 요구사항이 달라지면 HTML 코드만 고치면 되는 경우도 많습니다.


물론 인덱싱을 고려해야 하기 때문에 적정한 선에서 규칙과 형태는 갖추어져 있지만 그 스트레스가 하늘과 땅 차이 정도 되는 것 같습니다. 제 경우 폼 데이터를 콘트롤러에서 어느 정도 걸러내고 조합할 것과 매칭할 것을 처리한 뒤 그냥 모델 메소드로 던져 버립니다. 게다가 모델 오브젝트를 따로 사용하지 않기 때문에 DAO 가 없는 구성입니다. 여러 번 고민하다 가능하면 모델은 가볍게 구성하자는 쪽으로 정책을 세우고 코드를 만들었기 때문에 스키마가 있던 없던 어떤 필드가 추가되었던 삭제되었던 간에 부담이 적습니다.


* 배열을 한번에 받는다.


HTML 인풋 폼에서 체크박스 받을 때 가장 큰 감동을 느끼게 되는 대목입니다. 또는 하나의 이름으로 여러 개의 텍스트 타입의 INPUT 을 받을 때도 좋습니다.


NodeJS 로 서버사이드 코드를 만들고 있기 때문에 이질감이 없습니다. 그냥 배열 타입 체크해서 적당히 조작하여 저장하면 끝입니다. 이렇게 저장되 배열의 꺼내올 때도 그대로 배열이기 때문에 루프 돌려서 출력해주면 완벽히 입력된  그대로의 값을 출력할 수 있습니다.


게다가 이 배열을 검색하거나 매칭해야하는 경우도 상상하는 그대로 작동합니다. (포스트그레스도 가능합니다)


* 도큐멘트를 임베딩 할 수 있다.


이게 어떻게 비교 표현해야 할지 적당한 단어가 없지만 테이블 안에 테이블을 선언하여 저장할 수 있다는 점입니다.


보통 게시판을 보면 각 게시물 아래 댓글이 달리는 구조를 상상해보면 두 개의 테이블이 연결되어 있어서 FK, JOIN 등의 작업을 하지만 몽고DB 는 하나의 게시물 안에 댓글도 모두 담을 수 있다는 것으로 이해할 수 있습니다. 물론 그렇게 하지 않기를 권합니다만 게시물과 댓글이 아닌 경우 유용하게 사용할 수 있습니다.


이게 아주 유용한 경우는 배열을 저장할 경우입니다.


MySQL 에서 배열을 저장할 경우 가장 간편한 방법은 스트링으로 쪼개어 저장해 두는 것입니다. 아 이 얼마나 못생긴 일입니까... 최근 디스플레이 코드(요즘은 뭐라고 하는지 모르겠네요. 게시물을 별도로 추출하여 메인화면에 배치하는 등의 과정을 위한 추가 데이터들)를 작업하는 경우가 있어서 메인화면 처음으로 출력할 게시물에 이에 해당하는 참고할 만한 링크 타이틀을 조작하는 코드가 필요해 작업을 하게 되었습니다.


main title, description, link 에 따라다니는 한 개에서 여러 개가 될 수 있는 related title, link 의 데이터들을 조작하는 것입니다. 전형적인 has many 관계의 구조입니다. 데이터의 규모가 작고 변동 사항도 적고 연결 관계도 강하지 않은 이런 류의 모델을 MySQL 로 설계한다면 두개의 테이블을 쓰는 경우가 보통입니다. 아니면 스트링으도 쪼개거나... 몽고DB 에서는 별도의 콜렉션 없이 하나의 도큐멘트에서 처리가 가능합니다. 단순히 처리가 가능한 수준을 넘어 편하게 가능합니다.


게다가 모델이 단순해지면서 수정/삭제도 단순해집니다.

HTML에서 받은 INPUT 데이터가 없으면 없는 대로 있으면 있는 대로 저장하면 되니까요. 


아직까지 몽고DB 의 핵심인 맵/리듀스 나 샤딩 등의 고급 기능을 사용하지 않지만 이 정도만 되도 내가 필요한 데이터베이스 시스템이구나 하는 만족감이 듭니다.


NodeJS 와 함께 작업한다면 더 깔끔한 코드와 코딩 과정을 경험하게 될 것입니다.



아래 코드는 디스플레이 코드 관리 코드의 콘트롤러 코드의 일부입니다. 아래 함수 하나로 레코드 생성/수정이 가능합니다. 여전히 흠잡을 만한 구석이 많겠지만 이걸 PHP & MySQL 로 했으면 별써 때려치고 그만 두었을... 것 같습니다.


exports.createDisplay = function (req, res) {

    var postData = req.body;


    try {

        check(postData.display, '코드를 입력해주세요').notEmpty();


        var args = {

            display: postData.display,

            type: code.typeCode.show,

            category: postData.category || '',

            theme: Array.isArray(postData.theme) ? postData.theme : [postData.theme],

            title: postData.title || '',

            subtitle: postData.subtitle || '',

            excerpt: postData.excerpt || '',

            author: postData.author || '',

            tag: postData.tag || '',

            image: postData.image || '',

            caption: postData.caption || '',

            link: postData.link,

            relatedLink: []

        };


        if (Array.isArray(postData['related_title']) && postData['related_title'].length > 0) {

            for (var i = 0; i < postData['related_title'].length; i++) {

                if (postData['related_title'][i]) {

                    args.relatedLink.push({

                        title: postData['related_title'][i],

                        link: postData['related_link'][i],

                        flag: postData['related_flag'][i]

                    });

                }

            }

        } else {

            if (postData['related_title']) {

                args.relatedLink.push( {

                    title: postData['related_title'],

                    link: postData['related_link'],

                    flag: postData['related_flag']

                });

            }

        }


        if (postData._id) {

            args.updatedAt = postData.updateAt ? new Date(postData.updateAt) : new Date();


            display.updateDisplay(postData._id, args, function (error, result) {

                res.json(!error);

            })

        } else {

            args.createdAt = postData.updateAt ? new Date(postData.updateAt) : new Date();


            display.createDisplay(args, function (error, result) {

                res.json(!error);

            });

        }


    } catch (e) {

        res.json({msg: e.message});

        clog.warn('input parameter miss');

        clog.error(e.message);

    }

};


exports.createDisplay = function (document, callback) {

    var docs = db.collection(collection);


    docs.insert(document, {safe: true}, function (error, result) {

        callback(error, result[0]);

    });

};



exports.updateDisplay = function (display_id ,document, callback) {

    var docs = db.collection(collection);


    docs.update({ _id: db.ObjectID.createFromHexString(display_id) }, { $set: document }, { multi: true }, function (error, result) {

        callback(error, document);

    });

};






'Development > Coding' 카테고리의 다른 글

나눔고딕 적용시 문제점  (1) 2014.02.03
Dart lang  (0) 2013.11.15
Express 와 별도의 파일업로더 사용시 주의점  (0) 2013.01.23
PHP 변수 초기화 및 조건부 할당  (0) 2012.11.08
KCP 결제 모듈 적용기  (0) 2012.10.11