Notepad
Published 2021. 10. 15. 08:25
[MongoDB] 도큐먼트 지향 데이터 dev/DB

도큐먼트 지향 데이터

1.스키마 설계 원리

  • 데이터베이스 스키마 설계
    • 데이터베이스 시스템의 기능과 데이터의 특성, 애플리케이션의 요구사항이 주어졌을 때 데이터에 대한 최적의 표현을 찾아내는 과정
    • 최적의 스키마 설계는 사용하는 데이터베이스에 대한 깊은 지식과 애플리케이션의 요구사항에 대한 현명한 판단, 오랜 경험에서 나옴
    • 좋은 설계는 애플리케이션 확장과 성능을 고려한 변경과 같은 실험과 반복으로부터 나옴
  • RDBMS와 MongoDB
    • RDBMS
      • 정규화된 데이터 모델을 권장
      • 질의성 보장 및 데이터 무결성에 도움
      • 패턴이 확립되어 있어 일대다, 다대다 관계 등의 생성 방법이 기존 RDBS 사용자들에게 익숙함
    • MongoDB
      • 스키마 설계 규칙 없음
  • 데이터베이스 시스템 모델링 고려시 제기할 수 있는 질문
    • 애플리케이션의 액세스 패턴은 무언인가?
      • 읽기/쓰기 비율은 어떻게 되는가?
      • 쿼리는 키를 찾는 정도로 쉬운가 아니면 복잡한가?
      • 집계가 필요한가?
      • 데이터는 얼마나 저장되나?
    • 데이터베이스에는 어떤 기능이 있는가?
    • 좋은 고유 식별자(unique id)와 프리이머리 키를 무엇으로 만드는가?

2.전자상거래 데이터 모델 설계
2.1스키마 기본

  • RDBMS
    • 조인이 가능하기에 다중 테이블 스키마 구조
  • MongoDB
    • 컬렉션에 대한 스키마가 필요하지 않기에 어떠한 도큐먼트라도 받아들이는 구조
  • 상품 샘플 도큐먼트
{
    _id: ObjectId("4c4b1476238d3b4dd5003981"),  # 1.고유 객체 ID
    slug: "wheelbarrow-9092",                   # 2.고유 슬러그
    sku: "9092",
    name: "Extra Large Wheelbarrow",
    description: "Heavy duty wheelbarrow...",
    details: {                                  # 3.중첩 도큐먼트
        weight: 47,
        weight_units: "lbs",
        model_num: 4039283402,
        manufacturer: "Acme",
        color: "Green"
    },
    total_reviews: 4,
    average_review: 4.5,
    pricing: {
        retail: 589700,
        sale: 489700,
    },
    price_history: [                            # 4.일대다 관계
        {
            retail: 529700,
            sale: 429700,
            start: new Date(2010, 4, 1),
            end: new Date(2010, 4, 8)
        },
        {
            retail: 529700,
            sale: 529700,
            start: new Date(2010, 4, 9),
            end: new Date(2010, 4, 16)
        }
    ],
    primary_category: ObjectId("6a5b1476238d3b4dd5000048"),
    category_ids: [                             # 5.다대다 관계
        ObjectId("6a5b1476238d3b4dd5000048"),
        ObjectId("6a5b1476238d3b4dd5000049")
    ],
    main_cat_id: ObjectId("6a5b1476238d3b4dd5000048"),
    tags: ["tools", "gardening", "soil"]
}
  • 고유 객체 ID: MongoDB 객체 ID
  • 고유 슬러그
    • 사용자 친화적인 고유 주소(permalinks)
    • 도큐먼트에 대한 URL을 생성할 때는 슬러그 필드를 만들 것을 권장
    • 고유 인덱스를 생성하여 해당 필드의 값이 빠른 쿼리 접근성과 고유성을 보장하도록 함
  • 중첩 도큐먼트
    • 서브 도큐먼트로 이미 존재하는 도큐먼트 내부에 또 다른 것들을 찾게 해준다는 점에서 _id 필드와는 전혀 다름
  • 일대다 관계
    • 다른 컬렉션에 있는 도큐먼트와 관계 설정이 필요할 때, 다른 컬랙션의 값을 도큐먼트가 오직 하나의 주요 값을 가지는 관계
  • 다대다 관계
    • RDBMS
      • 조인 테이블 이용하여 다대다 관계 표현
    • MongoDB
      • 조인을 지원하지 않지만 다대다 관계 표현을 위해서는 객체 ID 값을 가지는 필드 필요
      • 집계 시에는 조인과 유사한 기능 제공($lookup)
    • 관계 구조
      • 카테고리 샘플 도큐먼트
{
    _id: ObjectId("6a5b1476238d3b4dd5000048"),
    slug: "gardening-tools",
    name: "Gardening Tools",
    description: "Gardening gadgets galore!",
    parent_id: ObjectId("55804822812cb336b78728f9"),
    ancestors: [
        {
            name: "Home",
            _id: ObjectId("558048f0812cb336b78728fa"),
            slug: "home"
        },
            Listing 4.2 A category document
            80 CHAPTER 4 Document-oriented data
        {
            name: "Outdoors",
            _id: ObjectId("55804822812cb336b78728f9"),
            slug: "outdoors"
        }
    ]
}
- 카테고리에 속해 있는 모든 상품에 대한 질의
  - db.products.find({category_ids: ObjectId('6a5b1476238d3b4dd5000048')})
- 어떤 상품이 속해 있는 모든 카테고리를 알기 위해서는 $in 연산자 사용
  - db.categories.find({_id: {$in: product['category_ids']}})

2.2사용자와 주문

  • 아이템, 가격, 배송 주소를 가지고 있는 전자상거래 샘플 도큐먼트
{
    _id: ObjectId("6a5b1476238d3b4dd5000048"),
    user_id: ObjectId("4c4b1476238d3b4dd5000001"),
    state: "CART",
    line_items: [
        {
            _id: ObjectId("4c4b1476238d3b4dd5003981"),
            sku: "9092",
            name: "Extra Large Wheelbarrow",
            quantity: 1,
            pricing: {
                retail: 5897,
                sale: 4897,
            }
        },
        {
            _id: ObjectId("4c4b1476238d3b4dd5003982"),
            sku: "10027",
            name: "Rubberized Work Glove, Black",
            quantity: 2,
            pricing: {
                retail: 1499,
                sale: 1299
            }
        }
    ],
    shipping_address: {
        street: "588 5th Street",
        city: "Brooklyn",
        state: "NY",
        zip: 11215
    },
    sub_total: 6196
}
  • 주소와 지불 방식을 가지고 있는 사용자 샘플 도큐먼트
{
    _id: ObjectId("4c4b1476238d3b4dd5000001"),
    username: "kbanker",
    email: "kylebanker@gmail.com",
    first_name: "Kyle",
    last_name: "Banker",
    hashed_password: "bd1cfa194c3a603e7186780824b04419",
    addresses: [
        {
            name: "home",
            street: "588 5th Street",
            city: "Brooklyn",
            state: "NY",
            zip: 11215
        },
        {
            name: "work",
            street: "1 E. 23rd Street",
            city: "New York",
            state: "NY",
            zip: 10010
        }
    ],
    payment_methods: [
        {
            name: "VISA",
            payment_token: "43f6ba1dfda6b8106dc7"
        }
    ]
}

2.3상품평

  • 상품평을 나타내는 샘플 도큐먼트
{
    _id: ObjectId("4c4b1476238d3b4dd5000041"),
    product_id: ObjectId("4c4b1476238d3b4dd5003981"),
    date: new Date(2010, 5, 7),
    title: "Amazing",
    text: "Has a squeaky wheel, but still a darn good wheelbarrow.",
    rating: 4,
    user_id: ObjectId("4c4b1476238d3b4dd5000042"),
    username: "dgreenthumb",
    helpful_votes: 3,
    voter_ids: [
        ObjectId("4c4b1476238d3b4dd5000033"),
        ObjectId("7a4f0376238d3b4dd5000003"),
        ObjectId("92c21476238d3b4dd5000032")
    ]
}

3.실제적 세부사항: 데이터베이스, 컬렉션, 도큐먼트
3.1데이터베이스

  • 컬랙션과 인덱스의 물리적 모음과 동시에 네임스페이스
  • 데이터베이스 관리
    • 데이터베이스 생성하는 별도의 다른 방법은 없음
    • 데이터베이스 내의 컬렉션에 쓰기를 하면 자동으로 생성
# 생성 및 연결
connection = Mongo::Client.new( [ '127.0.0.1:27017' ], :database => 'garden' )
db = connection.database

# 데이터베이스 선택(데이터베이스 인스턴스만 생성된 상태)
use garden

products = db['products']
products.insert_one({:name => "Extra Large Wheelbarrow"})
# 위 명령어 실행 후 컬렉션에 도큐먼트가 최초 생성되면서 컬렉션 생성 후 데이터베이스 생성

# 컬렉션 내 모든 도큐먼트 삭제
products.find({}).delete_many

# 컬렉션 삭제
products.drop

# MongoDB 셸에서 데이터베이스 삭제
use garden
db.dropDatabase();
  • 데이터 파일과 할당
    • 데이터베이스가 생성될 때 MongoDB는 몇 가지 데이터 파일을 디스크에 할당
      • 저장 데이터: 모든 컬렉션, 인덱스, 데이터베이스에 대한 메타데이터
      • 저장 위치: dbPath에서 지정한 디렉터리에 저장되며, 지정되지 않았다면 /data/db에 저장
      • 파일명: 데이터베이스의 이름을 따라 정해짐
        • garden -> garden.ns
    • 사용하는 공간과 할당된 공간 확인 명령어
> db.stats()
{
    "db" : "garden",
    "collections" : 3,
    "objects" : 5,
    "avgObjSize" : 49.6,
    "dataSize" : 248,           # BSON 객체의 실제 크기
    "storageSize" : 12288,      # 컬렉션이 증가 대비한 여분 공간 및 삭제되었지만 아직 할당 안된 공간 포함
    "numExtents" : 3,
    "indexes" : 1,
    "indexSize" : 8176,         # 인덱스 전체 크기
    "fileSize" : 201326592,     # 데이터베이스에 할당된 파일의 전체 크기
    "nsSizeMB" : 16,
    "dataFileVersion" : {
        "major" : 4,
        "minor" : 5
    },
    "ok" : 1
} 

3.2컬렉션

  • 구조적 또는 개념적으로 비슷한 도큐먼트를 담고 있는 컨테이너
  • 컬렉션 관리
    • 별도의 명령어 없이 도큐먼트를 네임스페이스에 삽입하는 것으로 컬렉션 생성
# 컬렉션 생성
db.createCollection("users")

# 컬렉션 크기 지정 생성
db.createCollection("users", {size: 20000})

# .을 사용한 가상 네임스페이스 생성(편의를 위해 . 추가.다른 컬렉션과 동일한 처리)
products.categories
products.images
products.reviews

# 컬렉션 이름 변경
db.products.renameCollection("store_products")
  • 캡드 컬렉션
    • 높은 성능의 로깅 기능을 위해 설계됨
    • 일반 컬렉션과 달리 고정된 크기를 갖는 차이점으로 더 이상 공간이 없게 되면 도큐먼트 삽입 시 가장 오래된 도큐먼트를 덮어씀
    • 캡드 컬렉션에 대한 사용자 행위 로깅 시뮬레이션
require 'mongo'

VIEW_PRODUCT = 0 # 사용자 행위 상수
ADD_TO_CART = 1
CHECKOUT = 2
PURCHASE = 3

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'garden')
client[:user_actions].drop
actions = client[:user_actions, :capped => true, :size => 16384]  # user_actions 컬렉션
actions.create

500.times do |n| # n을 반복자로 하여 500회 반복하여 샘플 도큐먼트 생성
    doc = {
        :username => "kbanker",
        :action_code => rand(4), # 0부터 3까지를 포함하는 임의의 값, 즉 0<=x<=3
        :time => Time.now.utc,
        :n => n
    }
    actions.insert_one(doc)
end 
> use garden
> db.user_actions.count();
160   # 캡드 컬렉션이라 크기 제한으로 160개 밖에 도큐먼트가 존재하지 않음

db.user_actions.find().pretty();
{
    "_id" : ObjectId("51d1c69878b10e1a0e000040"),
    "username" : "kbanker",
    "action_code" : 3,
    "time" : ISODate("2013-07-01T18:12:40.443Z"),
    "n" : 340
} 
{
    "_id" : ObjectId("51d1c69878b10e1a0e000041"),
    "username" : "kbanker",
    "action_code" : 2,
    "time" : ISODate("2013-07-01T18:12:40.444Z"),
    "n" : 341
}
{
    "_id" : ObjectId("51d1c69878b10e1a0e000042"),
    "username" : "kbanker",
    "action_code" : 2,
    "time" : ISODate("2013-07-01T18:12:40.445Z"),
    "n" : 342
}
... 생략 ...
# 크기 및 도큐먼트 최대 개수 지정 생성
> db.createCollection("users.actions",
 {capped: true, size: 16384, max: 100})
  • TTL 컬렉션
    • 특정 시간이 경과한 도큐먼트를 만료시킬 수 있는 기능을 제공하는 컬렉션
    • 실제로는 특별한 인덱스를 사용하여 구현한 것
    • TTL 인덱스 생성 및 삽입 예제
# review 도큐먼트 1시간 후 삭제 설정
> db.reviews.createIndex({time_field: 1}, {expireAfterSeconds: 3600})

# 도큐먼트 추가
> db.reviews.insert({
    time_field: new Date(),  # 타임스탬프 값 저장
    ... 생략 ...
 })
  • 시스템 컬렉션
    • MongoDB 내부에서 컬렉션을 부분적으로 사용
    • system.namespaces
      • 현재 사용 중인 데이터베이스에서 정의된 모든 네임스페이스 확인
> db.system.namespaces.find();
{ "name" : "garden.system.indexes" }
{ "name" : "garden.products.$_id_" }
{ "name" : "garden.products" }
{ "name" : "garden.user_actions.$_id_" }
{ "name" : "garden.user_actions", "options" : { "create" : "user_actions", "capped" : true, "size" : 1024 } }
- system.indexes
  - 각 인덱스에 대한 정보 확인
> db.system.indexes.find();
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "garden.products", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "garden.user_actions", "name" : "_id_" }
{ "v" : 1, "key" : { "time_field" : 1 }, "name" : "time_field_1", "ns" : "garden.reviews", "expireAfterSeconds" : 3600 }

3.3도큐먼트와 인서트

  • 도큐먼트 시리얼라이제이션, 타입 그리고 한계
    • 드라이버는 모든 도큐먼트를 저장하기 전에 BSON으로 시리얼라이즈되고 나중에 BSON으로 디시리얼라이즈 처리함

'dev > DB' 카테고리의 다른 글

MongoDB - 집계1  (0) 2021.10.28
MongoDB - Query Selectors(1)  (0) 2021.10.22
자바스크립트 셸을 통한 MongoDB  (0) 2021.09.30
MongoDB 핵심 기능 요약  (0) 2021.09.27
MongoDB 설치  (0) 2020.07.27
profile

Notepad

@Apio

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!