1.1 File System
Host 환경에 저장되는 File System에서는 해당 File을 사용하는 Process 중심으로 Data Modeling이 중요했습니다.
1.2 RDBMS System
Client / Server 환경에서 Data 공유를 위해 사용되었던 RDBMS System은 모든게 Server 위주였습니다.
그 당시에는 Hardware의 가격이 워낙 비쌌기 때문에, 최대한 저장공간을 아끼는 것이 중요했습니다.
즉 Data 중심의 Data Modeling이 가장 중요했습니다.
그래서 Data 정규화(Normalization)을 통해서 중복 Data를 최소화 하여 저장을 하였기 때문에
Data 조회시 Join을 해야만 했었고, 그로인해 Query 문은 복잡해 졌고, 수행시간 또한 길어 질 수 밖에 없었습니다.
그리고 저장을 하는 Data의 Type 또한 한정지을 수 밖에 없었습니다.
(문자, 숫자, 날짜 위주의 Text화 가능 Data)
1.3 No-SQL System
Cloud 환경에서 Big Data를 저장하고, 필요할 때 분석을 위해서 원하는 정보를 찾아보는게 중요한 시대가 되었습니다.
Hardware 가격은 예전보다 많이 저렴해 졌으며, Data 량 또한 과거와는 비교가 안될 정도로 많아졌기 때문에
상대적으로 저장공간을 아끼는 것보다는 Big Data를 빠르게 저장하고, 빠르게 읽어서 분석하는게 중요해졌습니다.
그리고 인해 중복Data의 저장은 큰 이슈가 아니며,
비정규화된 여러 가지 Data들 (사진, 음성, 동영상, 위치 등...)의 저장이 허용되어야 합니다.
빠른 분석을 위해서 같은 Data를 중복하여 저장하더라도, Join을 사용하지 않는게 더 선호됩니다.
Data Modeling은 Process, Data를 모두 고려해서 유연하게 설게하는게 중요합니다.
2. MongoDB Data Modeling
2.1 Rich Document Structure
RDBMS의 Column 안에 다른 Table 형식의 Record들을 저장하는건 힘듭니다.
그래서 정규화된 Table에 Data를 나누어서 저장하고,
조회시 Table 간에 Relationship을 이용하여 JOIN을 이용합니다.
MongoDB는 JSON 형식으로 Document를 저장하기 때문에
Field 안에 다른 JSON Document가 Array 형식으로 삽입될 수 있으며,
설계 당시에 생각하지 못했던 Data도 언제든지 Field를 추가하여 젖ㅇ이 가능합니다.
이러한 방법으로 Data Modeling 하는 것을 Rich Document Structure 라고 합니다.
2.2 JOIN 최소화
RDBMS 에서는 Data 저장공간 최적화를 위해서 정규화된 Data를 저장하기 때문에
조회시 많은 JOIN을 사용함으로써 코딩량을 증가시키고, 검색 성능을 저하하게 됩니다.
MongoDB는 어느 정도의 Data 중복을 허용하는 대신 JOIN을 최소화 할 수 있는 방법으로 설계하는게 좋습니다.
2.3 N : M Relation 지원
RDBMS에서는 N : M 관계로 설계가 안되었습니다.
그래서 별도의 Relationship을 표현하기 위한 Table을 생성하여서 2개의 Table 간의 N : M 관계 표현을 위해서
결과적으로 3개의 Table을 JOIN 해야만 했습니다.
하지만 MongoDB는 Array Type 제공 및 Document 간에 다른 Field를 가질 수 있는 특징으로 인하여
좀 더 유연하게 N : M 관계로 Data 구조 설계가 가능합니다.
2.4 Schema가 없습니다.
RDBMS에서는 User 별로 Schema가 주어져서 하나의 User (또는 한 Group의 User들)은
같은 Schema 안에 Table들을 생성하고 사용했습니다.
MongoDB는 User 계정은 오직 인증의 의미만 가지고 있기 때문에 Schema를 고려한 설계를 할 필요가 없습니다.
3. MongoDB 설계 고려사항
- Field 별 Data의 크기 : 소량의 Data가 저장되는 Field, 대용량 Data가 저장되는 Field
- Field 참조 빈도 : 매일 자주 참조되는 Field, 한 달에 한번, 1년에 한번 조회되는 Field
- Field Access Pattern : Write 후 Read만 하는지, Read/Write가 자주 발생하는지, Write가 여러번 되며 가끔 Read 되는지
- Field 보관 기간 : 얼마나 오랫동안 보관되어야 하는지
4. MongoDB Data Modeling Pattern
4.1 Rich Document
위 그림은 RDBMS 에서 Data Modeling에 사용되는 그림입니다.
왼쪽은 Object Oriented 로 Diagram을 표현했으며,
오른쪽은 사용하는 Entity Relationship Diagram의 한 예입니다.
주문(Order)은 주문상세(Order_detail)과 땔 수가 없는 밀접한 관계에 있습니다.
주문이 없는 주문상세는 의미가 없으며, 해당 주문이 삭제되면 주문상세 또한 같이 삭제되어야 합니다.
사실상 주문과 주문상세는 하나의 의미를 가진 Data지만, RDBMS의 정규화된 Data Modeling으로 인하여
별도의 Table로 생성하여 Relationship을 가지게 된 경우 입니다.
반면 부서(Department) 와 직원(Employee) 같이 따로 있어도 의미 부여가 가능하며,
언제든지 그 관계를 맺고 끊을 수 있으며, 다른 Element 로 관계 변경이 가능합니다.
이런 관계를 약한 관계를 가졌다고 표현합니다.
4.1.1 Embedded Document (내장형 Document)
RDBMS의 Record를 MongoDB에서는 Document 라는 용어로 표현합니다.
MongoDB에서는 Document 안에 다른 Document 형식의 Data를 포함 시킬 수 있습니다.
이 것을 내장 문서 (Embedded Document)라고 표현합니다.
Order와 Order_detail의 관계와 같은 강한 관계를 가진 경우에는
Order_detail의 정보를 가진 Document를 Order Document 안에 포함시키는 것이 좋습니다.
조회시 JOIN을 사용할 필요도 없어지며,
해당 Data의 수명(Life Cycle)도 함께하게 되며,
오히려 저장공간도 RDBMS 보다 절약됩니다. (Relationship에 사용되는 Primary Key와 Foreign Key가 필요없습니다.)
먼저 Orange for Oracle 을 이용하여 RDBMS에서의 방법입니다.
CREATE TABLE SALES.ORD ( ORD_ID VARCHAR(32) PRIMARY KEY, CUST_NAME VARCHAR(32), EMP_NAME VARCHAR(32), TOTAL INT, PAYMENT_TYPE VARCHAR(32), ORDER_FILLED CHAR(1) ); CREATE TABLE SALES.ORD_DETAIL (
ORD_ID VARCHAR(32),
SEQ INT,
PRODUCT_NAME VARCHAR(32),
ITEM_PRICE INT,
QTY INT,
PRICE INT,
PRIMARY KEY ( ORD_ID, SEQ ),
CONSTRAINT FK_ORD FOREIGN KEY (ORD_ID) REFERENCES SALES.ORD(ORD_ID)
);
INSERT INTO SALES.ORD VALUES ( '2012-09-012345' , 'Woman & Sports' , 'Magee' , 601100 , 'Create' , 'Y');
INSERT INTO SALES.ORD_DETAIL VALUES ( '2012-09-012345', 1 , 'Bunny Boots' , 135 , 500 , 67000 );
INSERT INTO SALES.ORD_DETAIL VALUES ( '2012-09-012345', 2 , 'Pro Ski Boots' , 380 , 400 , 152000);
SELECT O.ORD_ID , O.CUST_NAME , O.EMP_NAME , O.TOTAL , O.PAYMENT_TYPE , O.ORDER_FILLED,
D.SEQ , D.PRODUCT_NAME , D.ITEM_PRICE , D.QTY , D.PRICE
FROM SALES.ORD O , SALES.ORD_DETAIL D
WHERE O.ORD_ID = D.ORD_ID;
|
다음은 MongoDB에서의 실습입니다.
db.ord.insert( { ord_id : "2012-09-012345" , cust_name : "Woman & Sports" , emp_name : "Magee" , total : 601100 , payment_type : "Create" , order_filled : "Y" , item_id : [ { item_id : 1 , product_name : "Bunny Boots" , item_price : 135 , qty : 500 , price : 67000 } , { item_id : 2 , product_name : "Pro Ski Boots" , item_price : 380 , qty : 400 , price : 152000 } ] } ) db.ord.find().pretty() { "_id" : ObjectId("55c7d588ccbe945f98d69085"), "ord_id" : "2012-09-012345", "cust_name" : "Woman & Sports", "emp_name" : "Magee", "total" : 601100, "payment_type" : "Create", "order_filled" : "Y", "item_id" : [ { "item_id" : 1, "product_name" : "Bunny Boots", "item_price" : 135, "qty" : 500, "price" : 67000 }, { "item_id" : 2, "product_name" : "Pro Ski Boots", "item_price" : 380, "qty" : 400, "price" : 152000 } ] } |
4.1.2 Extent Document (확장형 Document)
앞서 설명한 Embedded Document와 크게 다르지 않습니다.
Embedded Document는 Order 정보에 Order_detail 정보를 함께 내장하여 저장한 방법이라면,
Extent Document는 Order 정보를 먼저 저장한 뒤 추가로 Order_detail 정보를 UPDATE 를 통해서 입력하는 방법입니다.
Database 상에 저장되는 Data 구조는 똑같습니다.
db.ord.drop() db.ord.insert( { ord_id : "2012-09-012345" , cust_name : "Woman & Sports" , emp_name : "Magee" , total : 601100 , payment_type : "Create" , order_filled : "Y" } ) db.ord.find( { ord_id : "2012-09-012345" } , { _id : 1 } ) { "_id" : ObjectId("55c7d84eccbe945f98d69086") } db.ord.update( { _id : ObjectId("55c7d84eccbe945f98d69086") } , { $set : { item_id : [ { item_id : 1 , product_name : "Bunny Boots" , item_price : 135 , qty : 500 , price : 67000 } , { item_id : 2 , product_name : "Pro Ski Boots" , item_price : 380 , qty : 400 , price : 152000 } ] } } ) db.ord.find().pretty() { "_id" : ObjectId("55c7d84eccbe945f98d69086"), "ord_id" : "2012-09-012345", "cust_name" : "Woman & Sports", "emp_name" : "Magee", "total" : 601100, "payment_type" : "Create", "order_filled" : "Y", "item_id" : [ { "item_id" : 1, "product_name" : "Bunny Boots", "item_price" : 135, "qty" : 500, "price" : 67000 }, { "item_id" : 2, "product_name" : "Pro Ski Boots", "item_price" : 380, "qty" : 400, "price" : 152000 } ] } |
4.1.3 Rich Document의 장단점
* 장점
- Query가 단순해지고 JOIN을 사용하지 않기 때문에 Document 단위의 Data저장에 효과적이며 빠릅니다.
- Data 보안에 효과적입니다.
- Data 구조가 강한 관계를 표현하기에 효과적입니다.
* 단점
- Embedded 되는 Document의 크기는 최대 16MB 범위에서 가능합니다.
- Embedded 되는 Document가 없으면 적합하지 않습니다.
- Data 구조가 유연한 관계에 대해서는 적합하지 않습니다.
4.2 Link
RDBMS의 Primary-Key , Foreign-Key 연결 구조와 유사합니다.
MongoDB는 ObjectId를 통해 연결합니다.
db.ord.drop() db.ord.insert( { ord_id : "2012-09-012345" , cust_name : "Woman & Sports" , emp_name : "Magee" , total : 601100 , payment_type : "Create" , order_filled : "Y" } ) o = db.ord.findOne( { "ord_id" : "2012-09-012345" } , { _id : 1 } ) { "_id" : ObjectId("55c7dd7cccbe945f98d69087") } db.ord_detail.insert( { ord_id : "2012-09-012345" , item_id : [ { item_id : 1 , product_name : "Bunny Boots" , item_price : 135 , qty : 500 , price : 67000 } , { item_id : 2 , product_name : "Pro Ski Boots" , item_price : 380 , qty : 400 , price : 152000 } ] , ordid_id : ObjectId("55c7dd7cccbe945f98d69087") } ) db.ord_detail.findOne( { ordid_id : o._id } ) { "_id" : ObjectId("55c7deb5ccbe945f98d69088"), "ord_id" : "2012-09-012345", "item_id" : [ { "item_id" : 1, "product_name" : "Bunny Boots", "item_price" : 135, "qty" : 500, "price" : 67000 }, { "item_id" : 2, "product_name" : "Pro Ski Boots", "item_price" : 380, "qty" : 400, "price" : 152000 } ], "ordid_id" : ObjectId("55c7dd7cccbe945f98d69087") } |
위 방법은 사용자가 직접 ord의 ObjectId를 검색한 뒤에 ord_detail에 저장을 했습니다.
DBRef 함수를 이용하면 검색없이 구현이 가능합니다.
db.ord.drop() db.ord_detail.drop() o = { ord_id : "2012-09-012345" , cust_name : "Woman & Sports" , emp_name : "Magee" , total : 601100 , payment_type : "Create" , order_filled : "Y" } db.ord.save(o) db.ord_detail.insert( { ord_id : "2012-09-012345" , item_id : [ { item_id : 1 , product_name : "Bunny Boots" , item_price : 135 , qty : 500 , price : 67000 } , { item_id : 2 , product_name : "Pro Ski Boots" , item_price : 380 , qty : 400 , price : 152000 } ] , ordid_id : [ new DBRef ("ord" , o._id) ] } ) db.ord_detail.find().pretty() { "_id" : ObjectId("55c7e1bcccbe945f98d6908a"), "ord_id" : "2012-09-012345", "item_id" : [ { "item_id" : 1, "product_name" : "Bunny Boots", "item_price" : 135, "qty" : 500, "price" : 67000 }, { "item_id" : 2, "product_name" : "Pro Ski Boots", "item_price" : 380, "qty" : 400, "price" : 152000 } ], "ordid_id" : [ DBRef("ord", ObjectId("55c7e178ccbe945f98d69089")) ] } db.ord.find().pretty() { "_id" : ObjectId("55c7e178ccbe945f98d69089"), "ord_id" : "2012-09-012345", "cust_name" : "Woman & Sports", "emp_name" : "Magee", "total" : 601100, "payment_type" : "Create", "order_filled" : "Y" } |
* 장점
- 별도의 Collection에 저장되기 때문에 Document 크기 제약이 없습니다.
- Business Rule 상 별도로 처리되는 유연한 관계를 표현하기에 효과적입니다.
* 단점
- 조회시 Link가 필요하므로 Embedded보다 성능이 떨어집니다.
- Collection 개수가 증가하므로 관리 비용이 많이 듭니다.
4.3 N : M Relationship Pattern
RDBMS에서는 모든 관계를 1 : 1 또는 1 : N 으로 표현해야 합니다.
그리고 모든 Data를 미리 정의해논 타입에 맞게끔 정형화 해서 저장해야 합니다.
MongoDB는 N : M 으로 표현이 가능한데,
가능한 이유는 바로 비정형화된 Data 처리가 가능하기 때문입니다.
예를 들어보겠습니다.
StarCraft 에서 Terran의 유닛 중
Marine은 Riffle과 Steampack을 가지고 있으며,
Firebat은 Frameblaster와 Steampack을 가지고 있습니다.
Medic은 Medikit을 가지고 있습니다.
Marine, Firebat, Steampack 에 대해서 살펴보면 누가봐도 N : M 관계인것을 알 수 있습니다.
이걸 RDBMS에서는 어떻게 표현해야 할까요 ?
중간에 M:N Relation을 표현하는 Table을 하나 더 만들어서 Unit Table과 1 : N 관계로 정의하고
Equipment Table 과도 1 : N으로 정의할 수 밖에 없습니다.
MongoDB는 비정형화된 Data 입력이 가능하므로, 그냥 넣으면 됩니다.
배열로 하든, Field를 추가하든 편한대로 막하면 됩니다.
나중에 새로운 관계가 추가되었다면 ? 그냥 UPDATE로 해당 관계만 추가하면 됩니다.
db.units.insert( { name : "Marine" , eq : [ "Riffle" , "Steampack" ] } ) db.units.insert( { name : "Firebat" , eq : [ "Frameblaster" , "Steampack" ] } ) db.units.insert( { name : "Medic" , eq : [ "Medikit" ] } ) db.units.find() { "_id" : ObjectId("55c7e9c1ccbe945f98d6908b"), "name" : "Marine", "eq" : [ "Riffle", "Steampack" ] } { "_id" : ObjectId("55c7e9dfccbe945f98d6908c"), "name" : "Firebat", "eq" : [ "Frameblaster", "Steampack" ] } { "_id" : ObjectId("55c7e9f3ccbe945f98d6908d"), "name" : "Medic", "eq" : [ "Medikit" ] } db.equipment.save( { name : "Riffle" , unit1 : "Marine" } ) db.equipment.save( { name : "Frameblaster" , unit1 : "Firebat" } ) db.equipment.save( { name : "Steampack" , unit1 : "Marine" , unit2 : "Firebat" } ) db.equipment.save( { name : "Medikit" , unit1 : "Medic" } ) db.equipment.find() { "_id" : ObjectId("55c7ea21ccbe945f98d6908e"), "name" : "Riffle", "unit1" : "Marine" } { "_id" : ObjectId("55c7ea3accbe945f98d6908f"), "name" : "Frameblaster", "unit1" : "Firebat" } { "_id" : ObjectId("55c7ea5dccbe945f98d69090"), "name" : "Steampack", "unit1" : "Marine", "unit2" : "Firebat" } { "_id" : ObjectId("55c7ea6fccbe945f98d69091"), "name" : "Medikit", "unit1" : "Medic" } |
4.4 Inheritance Pattern
Object-Oriented Programming에서의 그 상속이 맞습니다. 맞고요. Object간의 공통 특징은 부모 Clss에 정의를 해두고, 그것을 상속 받은 Object는 자신만 가지고 있는 속성들을 정의하는 방식입니다. 예를 들어보겠습니다. 또 StarCraft입니다. 이번엔 Protoss 로 하겠습니다. 모든 Protoss Unit은 이쁘고 고급집니다. ㅎ 생명력(Life)와 보호막(Shield) 을 가지고 있습니다. 물론 싸우려면 무기(Weapon) 도 있어야겠죠. 그중 Zealot은 빠른발(FastFoot) 과 광선검 2개 (Two Sword)를 가지고 있으며, Dark Templer 는 눈에 띄지 않으며 (Clocking) 광선검 1개 (Scythe)를 가지고 있습니다. 그리고 2마리가 합쳐져서 Dark Archon으로 변신이 가능합니다. 이걸 RDBMS의 Table로 표현을 하면 다음과 같게 설계를 할 수 있습니다. |
CREATE TABLE Protoss ( Name VARCHAR(32), Life INT, Shield INT, Weapon VARCHAR(32), Shoes VARCHAR(32), Shirts VARCHAR(32), Fusion VARCHAR(32) ) INSERT INTO Protoss VALUES ( 'Zealot' , 100 , 50 , 'Two Swords' , 'FastFoot', NULL, NULL ); INSERT INTO Protoss VALUES ( 'Dark Templer' , 40 , 80 , 'Scythe' , NULL, 'Cloking' , 'Dark Archon'); |
Name을 보고 특정 Unit에 대해서만 Shoes 와 Shirts, Fusion Column을 참조하는 형식입니다.
해당 속성을 사용하지 않는 Unit에 대해서도 값을 NULL로 넣어두고 라고 가지고 있어야만 합니다.
MongoDB는 비정형Data 표현이 가능하므로 좀 더 유연하게 정의가 가능합니다.
db.createCollection('Protoss') db.Protoss.insert( { name : 'Zealot' , life : 100 , shield : 50 , Weapon : 'Two Swords' , Shoes : 'Fast Foot' } ) db.Protoss.insert( { name : 'Dark Templer' , life : 40 , shield : 80 , Weapon : 'Scythe' , Shirts : 'Clocking' , Fusion : 'Dark Archon' } ) db.Protoss.find().pretty() { "_id" : ObjectId("55c7f2feccbe945f98d69092"), "name" : "Zealot", "life" : 100, "shield" : 50, "Weapon" : "Two Swords", "Shoes" : "Fast Foot" } { "_id" : ObjectId("55c7f335ccbe945f98d69093"), "name" : "Dark Templer", "life" : 40, "shield" : 80, "Weapon" : "Scythe", "Shirts" : "Clocking", "Fusion" : "Dark Archon" } |
4.5 Hierarchy Pattern
Data간의 상하 관계를 Link를 통해서 저장, 관리하는 방법입니다.
대표적인 예로는 기업의 조직 구성도를 들 수 있습니다.
Oracle의 EMP Table은 이미 계층구조로 생성되어 있습니다. SELECT A.EMPNO , A.ENAME , A.MGR , B.ENAME
FROM SCOTT.EMP A , SCOTT.EMP B
WHERE A.MGR = B.EMPNO (+)
MongoDB에서 앞서 입력해둔 emp Collection 을 이용하여 실습해 보겠습니다.
|
db.emp.find( {} , { _id : 0 } ) { "empno" : 7369, "ename" : "SMITH", "job" : "CLERK", "hiredate" : "17-12-1980", "sal" : 800, "deptno" : 20 } { "empno" : 7499, "ename" : "ALLEN", "job" : "SALESMAN", "hiredate" : "20-02-1981", "sal" : 1600, "comm" : 300, "deptno" : 30 } { "empno" : 7521, "ename" : "WARD", "job" : "SALESMAN", "hiredate" : "22-02-1981", "sal" : 1250, "comm" : 500, "deptno": 30 } ... { "empno" : 7934, "ename" : "CLERK", "job" : "CLERK", "hiredate" : "23-01-1982", "sal" : 1300, "deptno" : 10 } db.emp.insert( { empno:7939, ename : 'King' , job : 'President' } ) db.emp.update( { empno:7782 } , { $set : { mgr : 7939 } } ) db.emp.update( { empno:7934 } , { $set : { mgr : 7782 } } ) db.emp.find( {} , { _id : 0 , empno : 1 , ename : 1 , mgr : 1 } ) ... { "empno" : 7782, "ename" : "CLARK", "mgr" : 7939 } ... { "empno" : 7934, "ename" : "CLERK", "mgr" : 7782 } { "empno" : 7939, "ename" : "King" } db.emp.find( { mgr : 7939 } , { _id : 0 , empno : 1 , ename : 1 } ) { "empno" : 7782, "ename" : "CLARK" } db.emp.find( { mgr : 7782 } , { _id : 0 , empno : 1 , ename : 1 } ) { "empno" : 7934, "ename" : "CLERK" } |
댓글 없음:
댓글 쓰기