(몽고DB 완벽 가이드) Chapter 6. 특수 인덱스와 컬렉션 유형

이재문·2023년 11월 27일

6.1 공간 정보 인덱스

  • mongoDB는 지형 정보에 대한 쿼리를 지원하기 위해 지형 정보 인덱스를 가질 수 있다.
    • 2dsphere - 지구와 같은 구형태의 지형을 기반으로 하는 계산에 사용
    • 2d - x, y축으로 이뤄진 평면 지형을 기반으로 계산하는데 사용
  • 2dsphere를 사용하면 GeoJSON 형식으로 점, 선, 다각형의 기하 구조를 설정 할 수 있다.

    GeoJSON

    JSON 형태로 지형 데이터를 정의하는 포맷.

    GeoJSON의 타입에는 PointLineStringPolygonMultiPointMultiLineStringMultiPolygonGeometryCollection이 있다.

    위 타입들에서 볼 수 있듯이, 좌표점 뿐만이 아니라 선에서 다각형, 여러 도형들까지 지원.

    coordinates 필드는 type에 따라 여러 좌표점을 포함 할 수 있습니다.

  • 공간 정보 인덱스 ex
    • 점 - 경도, 위도의 좌표를 list에 넣는다. [경도, 위도]

      {
      		"name": "Seoul",
      		"loc": {
      					"type": "Point",
      					"coordinates": [10, 1]
      			}
      }
    • 선 - 점을 이으면 선이 된다.

      {
      		"name": "Busan",
      		"loc": {
      				"type": "LineString",
      				"coordinates": [[0,1], [0,2], [1,2]]
      		}
      }
    • 다각형 - 선과 비슷하게 배열로 만들지만 type 다름.

      {
      		"name": "Busan",
      		"loc": {
      				"type": "Polygon",
      				"coordinates": [[0,1], [0,2], [1,2]]
      		}
      }
  • 공간 정보 인덱스 생성 방법
    db.Map.createIndex({"loc": "2dsphere"})
    											ㄴ 필드명에 따라 도형 설정하는 도큐먼트 필드가 지정된다.

6.1.1 공간 정보 쿼리 유형

  • 교차, 포함, 근접 유형있음

  • geoWithin특정 지역에 완전히 포함된 항목을 쿼리할 수 있다.

    > db.Map.find({”loc”: “$geoWithin”: {”$geometry”: eastVillage}}})
    • esatVillage의 완전히 겹치는 지역을 반환
  • near: 주변 위치 쿼리

    > db.Map.find({"loc": {"$near": {"$geometry": esatvillage}}})
    • 정렬을 포함하는 공간 정보 연산자
    • 거리가 가장 가까운 곳부터 가장 먼 곳 순으로 반환

6.1.2 공간 정보 인덱스 사용

  • 특정 지역과 관련된 모양과 점이 포함된 컬렉션에서 공간 쿼리가 효율적일 수 잇다.

Ex

사용자가 현재 위치한 지역을 찾는다.
해당 지역 내 레스토랑 수를 보여준다.
지정된 거리 내에 있는 레스토랑을 찾는다.

  • 2dsphere 인덱스 사용

쿼리에서의 2D vs. 구면 기하학

쿼리 유형기하 구조 유형
$near (GeoJSON point, 2dsphere 인덱스)구면
$near (레거시 좌표, 2d 인덱스)평면
$geoNear (GeoJSON point, 2dsphere 인덱스)구면
$geonear (레거시 좌표, 2d 인덱스)평면
$nearSphere (GeoJSON point, 2dsphere 인덱스)구면
$nearSphere (레거시 좌표, 2d 인덱스)구면
geoWithin {geometry}구면
geoWithin {box}평면
geoWithin {polygon}평면
geoWithin {center}평면
geoWithin {centerSphere}구면
$geoIntersects구면
  • 2d 인덱스는 구에서 평면과 거리 계산 지원.

  • 구 형태 쿼리는 2dsphere로 성능, 정확성 올림

  • goeNear 연산자는 집계연산자

  • $near 쿼리 연산자는 mongoDB의 확장 솔루션인 샤딩을 사용해 배포된 컬렉션에서 작동X

  • geoNear 명령과 $geoNear 를 사용하려면 컬렉션에 2dsphere 인덱스와 2d 인덱스가 최대 1개만 존재해야 함

  • $near, $geoWithin 같은 공간 정보 쿼리 연산자는 컬렉션이 여러 개의 공간 정보 인덱스를 갖도록 허용된다.

  • $geoNeargeoNear는 둘 다 위치 필드를 포함하지 않으므로 공간 정보 인덱스 제한이 존재

왜곡

  • 구 형태는 지도에 평면으로 시각화하면 왜곡이 존재

6.1.3 복합 공간 정보

  • 공간 정보 인덱스는 다른 필드와 묶어 쿼리 최적화 가능
  • ex
    - tags 필드 추가
    db.openStreetMap.createIndex({"tags": 1, "location": "2dsphere"})
    db.openStreetMap.find({"loc": {"$geoWithin":
    		{"$geometry": hellsKitchen.geometry}},
    		"tags": "pizza"})
    • 2dsphere 필드 앞, 뒤로 인덱스 필드를 추가 가능
    • 더 선택적인 필드를 먼저 필터링

6.1.4 2d 인덱스

  • 완전 평평한 표면이라 가정

  • 평면형 지도 2d 인덱스 사용

    db.hyrule.createIndex({"tile": "2d"})
  • 구체에 2d 사용하지 말자

  • GeoJSON 데이터는 2d 사용에 적합하지 않다.

  • 점만 인덱싱 가능. 점을 배열로 만들 순 있지만, 선은 아니다.

  • 도로를 점으로 저장했을 때, $geoWithin 쿼리에서 점 하나만 인식해도 일치하게 반환된다.

  • default 인덱스 값: -180, 180

    > db.hyrule.createIndex({"light-years": "2d"}, {"min": -1000, "max": 1000})
  • ex

    • 사각형 내 도큐먼트 쿼리

      db.hyrule.find({
      	tile: { 
      		$geoWithin: {
      			$box: [[10, 10], [100, 100]]
      		}
      	}
      })
    • 중심이 [-17, 20.5]이고, 반지름이 25인 원 내에 있는 도큐먼트 쿼리

      db.hyrule.find({
      	tile: {
      		$geoWithin: {
      			$center: [[-17, 20.5], 25]
      		}
      	}
      })
    • 다각형 도큐먼트 쿼리

      db.hyrule.find({
      tile: {
      	$geoWithin: {
      		$polygon: [[0, 0], [3, 6], [6, 0]]
      		}
      	}
      }]
    • polygon(다각형)은 점의 배열로 저장.

    • 배열의 마지막 점은 첫번 째 점에 이어짐.

6.2 전문 검색을 위한 인덱스

Text 인덱스

  • 제목, 설명 등 컬렉션 내에 있는 필드의 텍스트와 일치시키는 키워드 쿼리를 사용하려면 text 인덱스를 사용
  • text 인덱스는 텍스트를 빠르게 검색하는 기능 제공
    • 토큰화 (tokenization)
    • 정지 단어 (stopword)
    • 형태소 분석 (stemming) 등
  • 필요한 key 개수는 인덱싱되는 필드의 단어 개수에 비례
  • 성능에 부정적인 영향을 미치지 않을 때 생성하도록 하자.
  • 백그라운드 인덱스 생성을 권장

6.2.1 텍스트 인덱스 생성

  • 텍스트에 검색을 실행하려면 text 인덱스가 필요하다.
  • ex
    // title과 body 필드 내 용어를 기반으로 인덱스 생성 
    db.articles.createIndex({"title": "text",
    												"body": "text"})
  • text 인덱스는 각 필드가 동등하게 고려된다.
  • 가중치를 지정하여 필드의 중요도 제어 가능
    > db.articles.createIndex({"title": "text",
    												"body": "text"},
    												{"weights": {
    															"title": 3,
    															"body": 2}})													
    • 인덱스 삭제하고 재 생성으로 가중치 변경 가능
    • $** 에 인덱스를 만들면 도큐먼트의 문자열 필드에 인덱스 생성 가능
      • 모든 문자열 필드 인덱싱, 내장 도큐먼트와 배열에서 문자열 필드 검색

6.2.2 텍스트 검색

  • $text는 공백과 ., 구분 기호를 사용해 문자열 토큰화 하여, 검색 문자열에서 모든 토큰의 논리적 OR 을 수행한다.
  • ex
    • 공백으로 impart , crater , lunar 단어가 포함된 title 필드에서 검색한다(OR).

      db.articles.find({"$text": {"$search": "impart crater lunar"}},
      						{title: 1}).limit(10)
    • 관련성이 없는 결과값이 포함될 수 있다.

    • impart , crater , lunar 를 OR를 사용해 쿼리를 실행한다 - 쿼리가 매우 광범위하다.

    • 결과를 관련성에 따라 정렬하지 않는다.

      // "impact crater" 구문이 포함된 도큐먼트를 찾는다.
      // "impact crater" AND "lunar"로 처리
      db.articles.find({$text: {$search: "\"impact crater\" lunar"}},
      						{title: 1}
      						).limit(10)
      
      // "impact crater" AND ("lunar" OR "meteor")로 처리
      db.articles.find({$text: {$search: "\"impact crater\" lunar meteor"}},
      							{title: 1}
      						).limit(10)
      
      // "impact crater" AND ::lunar" AND "meteor"
      db.articles.find({$text: {$search: "\"impct crater\" \"lunar\" \"meteor\""}},
      					{title: 1}
      					).limit(10)
    • .메타데이터 $meta 연산자를 사용해 textScore로 필드를 지정하여 메타 데이터 필드에 저장된다.

      > db.articles.find({$text: {$saerch: "\" impact crater\" lunar"}},
      								{title:1, score: {$meata: "textScore"}}
      								).limit(10)
      
      >> {"_id": "2", "title": Schjellerup (crater)", "score": 2.85298713914}
    • 점수 순 결과 정렬 sort 호출

      > db.articles.find({$text: {$saerch: "\" impact crater\" lunar"}},
      								{title:1, score: {$meata: "textScore"}}
      								).sort({score: {$meta: "textScore"}}).limit(10)
      • 지정 해 놓은 동일한 필드명을 사용해야 한다.

6.2.3 전문 검색 최적화

  • ex
    db.blog.createIndex({"date": 1, "post": "text"})
    • 전문 인덱스를 “date”에 따라 작은 트리 구조로 쪼개며, 파티셔닝이라 한다.
    • 전문 인덱스를 분할해 특정 날짜에 대한 전문 검색을 빨리 할 수 있다.

6.2.4 다른 언어로 검색하기

  • 도큐먼트 입력 → 인덱스 필드 search → 기본 구성 단위로 형태소 분석
    • 언어에 따라 형태소가 달라 도큐먼트 언어 명시 필요

    • default: english

      db.users.createIndex({"profil": "text",
      											"interets": "text"},
      											{"default_language": "french"})
    • 도큐먼트 별로 language 설정 가능

      db.users.insert({"username": "jaemoon", lnaguage: "swedish"})

6.3 제한 컬렉션

일반적인 컬렉션

  • 동적으로 생성되고 추가적인 데이터에 맞춰 크기가 자동으로 늘어난다.

제한 컬렉션

  • 미리 생성되고, 크기가 고정된다.
  • 이미 가득 찬 컬렉션에 입력하면, 가장 오래된 도큐먼트 삭제 후 새로운 도큐먼트 삽입
  • 도큐먼트 삭제 불가
  • 도큐먼트 크기 ^ 갱신 불가
  • 디스크의 고정된 영역에 순서대로 기록된다.
  • 회전 디스크에서 쓰기를 빠르게 수행가능
  • TTL 인덱스
    • 와이어드타이거 스토리지 엔진에서 더 나은 성능 발휘 - 제한 컬렉션보다 권장됨
    • 날짜 유형 필드 값과 인덱스 TTL 값을 기반으로 일반 컬렉션에서 데이터가 만료되고 제거 됨
    • 제한 컬렉션은 샤딩 불가.
  • 로깅에 유용하다

6.3.1 제한 컬렉션 생성

  • ex
    • 10만 바이트 고정 크기로 제한 컬렉션 생성

      db.createCollection("my_collection", {"capped": true, "size": 100000});
    • 도큐먼트 100개로 제한하는게 아닌가??

      db.createCollection("my_collection2", {"capped": true, "size": 10000, "max": 100});
  • 제한 컬렉션이 생성되면 변경 불가
  • 일반 컬렉션 → 제한 컬렉션 가능
    • 1만 바이트 크기의 제한 컬렉션 변환 코드

      db.runCommand({"convertToCapped": "test", "size": 10000}), {"ok": true}

6.3.2 꼬리를 무는 커서

  • 결과를 모두 꺼낸 후에 종료되지 않는 커서
  • 종료가 되지 않으므로 컬렉션에 데이터 추가가 이뤄지면 새로운 결과를 바로 응답 가능
  • 입력 순서가 제한 컬렉션에만 추적 가능하기에 제한 컬렉션에만 사용 가능
  • 10분 후에 종료 됨. 이후에 컬렉션에 쿼리하는 로직 포함 필요

6.4 TTL 인덱스

  • TTL 인덱스를 이용해 도큐먼트에 유효 시간을 설정 가능. 시간이 되면 삭제 (캐싱에 유용)
    // expireAfterSecounds 초를 지나면 도큐먼트 삭제
    db.sessions.createIndex({"lastUpdated": 1}. {"expireAfterSecounds": 60*60*24})
  • 인덱스를 매분 마다 처리한다.
// runCommand로 expireAfterSecounds 수정 가능
db.runCommand({"collMod": "someapp.cache", 
							"index": 
						{"keyPattern": {"lastUpdated":1}, "expireAfterSeconds": 3600}})
  • 하나의 컬렉션에 TTL 인덱스를 여러 개 가질 수 있다.

6.5 GridFS로 파일 저장하기

  • GridFS : MongoDB 내 large objects 를 저장할 수 있는 파일 시스템
  • 장점
    • 아키텍처 스택을 단순화 할 수 있다. 이미 mongodb를 사용 중이라면 파일스토리지를 위한 별도의 도구 대신 GridFS를 사용하면 된다.
    • mongodb를 위해 설정한 기존의 복제나 자동 샤딩을 이용할 수 있다. 파일 스토리지를 위한 장애 조치와 분산 확장이 쉽다.
    • 사용자가 올린 파일을 저장할 때, 특정 파일시스템이 갖는 문제를 피할 수 있다. 같은 디레거리에 대량의 파일을 저장해도 문제 없다.
  • 단점
    • 성능이 느리다.
    • 도큐먼트 수정 X, 삭제 후 재 생성 O
    • mongoDB는 여러개 도큐먼트로 저장하므로 한 파일의 모든 청크에 동시에 락을 걸 수 없다.
  • 순차적인 방식으로 접근하려는 대용량 파일을 저장 할 때 최선의 패커니즘이다.

6.5.1 GridFS 시작하기: mongofiles

  • mongofiles 유틸리티로 GridFS 설치, 운영 가능
  • mongofiles는 GridFS에서 업로드, 다운로드, 목록 출력, 검색, 삭제 등에 사용된다.
    • put : 파일시스템으로부터 파일을 받아 GridFS에 추가
    • list : GridFS에 올린 파일 목록 확인
    • get : GridFS에서 파일을 받아 파일시스템에 저장

6.5.2 몽고DB 드라이버로 GridFS 작업하기

  • 모든 클라이언트 라이브러리는 GridFS API를 가진다.
  • pymongo의 GridFS API는 mongofiles APIdhk dbtkgkek.

6.5.3 내부 살펴보기

  • 대용량 파일을 청크로 나눈 후 각 청크를 도큐먼트로 저장할 수 있다.
  • 청크 컬렉션 구조
    • files_id - 청크에 대한 메타데이터를 포함하는 도큐먼트 _id
    • n - 다른 청크를 기준으로 하는 파일 내 청크 위치
    • data - 파일 내 청크 크기( byte)
    • _id - 파일 고유 ID
    • length - 파일 내용의 총 바이트 수
    • chunkSize - 파일을 구성하는 청크의 크기 (byte) default: 256kilobyte 조정 가능
    • uploadDate - GridFS에 파일이 저장된 시간
    • md5 - 서버에서 생성된 파일 내용의 MD5 체크섬
      • 암호 검사합(체크섬)은 파일 안에 있는 데이터를 해시(hash)한 해시값이라고도 불리며, 고정된 길이의 값으로 변환하는 복잡한 수학적인 연산(암호 알고리즘)에 의해 생성된다. 인가되지 않은 사람은 해당 검사합을 변경하지 않고는 데이터를 변경할 수 없다.
profile
이제부터 백엔드 개발자

0개의 댓글