Note 324 NoSQL, Mongo DB, Pymongo
SQL은 관계형 데이터베이스로 관계와 구조가 정해져 있기 때문에 스키마를 짜고, 데이터를 입력하는 데에 많은 시간이 소요된다. 하지만, 이미 짜여진 스키마를 토대로 데이터를 읽어오기만 하면 되어서 데이터를 읽어온 후에는 데이터가 어느 정도 정제가 되어 있을 것이다. 데이터베이스는 이런 SQL 외에도 NoSQL 이라는 것이 존재한다. 이번 포스팅에서는 NoSQL과 NoSQL 중에서도 문서형으로 데이터를 저장하는 Mongo DB에 대해 정리해보자.
NoSQL
- 등장배경
NoSQL은 2000년대에 들어서 Web 시장이 크게 발전하면서 저장해야 하는 데이터의 양이 폭발적으로 증가했을 때 등장했다. Web 데이터는 보통 XML, JSON 형태로 처리하는데 기존에 사용하던 관계형 데이터베이스로는 이를 처리하기가 힘들었고 저장공간, 속도 등이 떨어져서 이 DB를 더 큰 서버로 확장시켜서(수직확장) 데이터를 저장했어야 했다. 하지만, DB를 확장시킬 때 드는 비용이 너무 컸기 때문에 여러 대의 작은 서버를 사용하여 데이터를 저장하는 방법이 등장(수평확장)했고, 이 클러스터링 방법이 수직확장보다 비용이 덜 들었다.
- SQL vs NoSQL
1) 데이터 저장
- 관계형 DB는 미리 작성된 스키마를 기반으로 SQL을 이용해서 데이터를 테이블에 저장한다.
- NoSQL DB는 key-value, document, wide-column 형식 등의 방식으로 데이터를 저장한다.
2) 스키마(Schema)
- SQL에서는 고정된 형식의 스키마가 필요하다. 수정할 수 있지만, 수정하기 위해서는 DB를 오프라인으로 바꾸고 수정해야한다.
- NoSQL DB는 스키마의 형태가 SQL에 비해 유연하다. 스키마가 아예 없는 것은 아니지만, 행을 추가할 때 즉시 열을 추가한다던지, 모든 열에 대해 데이터를 반드시 입력하지 않아도 된다던지의 장점이 있다.
-> NoSQL에 스키마가 반드시 없는 것은 아니다. RDB가 데이터를 쓸 때 스키마를 맞춘다면, NoSQL DB는 데이터를 읽어올 때 스키마를 어느 정도 맞춰야 한다. 읽어올 때는 어느정도 사용자가 써놓은 형태에 따라 읽어와야 하기 때문에 결국 어떻게 쓰냐가 어떻게 읽어와야 하는지에 대한 영향을 미치게 된다. NoSQL이라고 너무 비정제된 데이터를 저장한다면 읽어오는 방법이 복잡할 것이다. 따라서 완벽하진 않더라도 적절한 스키마가 있는 것이 좋다.
3) 쿼리(Querying)
- 쿼리는 DB에 정보를 요청하는 행동이다. RDB는 테이블의 형식과 관계에 맞춰서 구조화된 쿼리 언어로 데이터를 요청한다.
- 비관계형 DB의 쿼리는 구조화된 데이터 보다는 데이터 그룹 자체를 조회하는 것에 초점을 두고 있다. 쿼리가 구조화 되지 않아도 데이터 요청이 가능하다. UnQL(Unstructured Query Language)라고 하기도 한다.
4) 확장성(Scalability)
- SQL은 보통 문제가 발생했을 때 더 큰 장치를 사용하는 수직 확장을 사용한다. 수직 확장을 위해서는 높은 메모리,CPU 등 고성능 하드웨어가 요구되기 때문에 비용이 많이 든다.
(SQL은 ACID의 원칙에 따라 트랜잭션이 동시에 발생할 수 없다. 따라서 하나의 DB에서 트랜잭션이 수행되면 다른 DB는 대기하고 있어야하니까 분산 컴퓨팅을 할 수 없어서 수평확장을 하지 않는 것이 좋다.)
- NoSQL로 구성된 DB는 문제가 발생했을 때 여러 대의 작은 장치들을 사용하는 수평 확장을 사용한다. 클라우드 서비스를 이용하고 수직확장보다 더 싼 서버를 여러 대를 두기 때문에 많은 트래픽을 처리할 수 있다. 또한, 클라우드 기반의 인스턴스에 NoSQL DB를 호스팅할 수 있어서, 수직확장보다 비용 효율성이 높다.
SQL을 사용해야 할 때
1) DB의 ACID 성질을 준수해야 할 때
ACID는 데이터베이스 내에서 일어나는 하나의 트랜잭션의 안전성을 보장하기 위함이라고 정리했다(Note 313 포스팅 참고). ACID 성질을 사용하면 데이터를 처리할 때 이상 징후가 줄어들고, DB의 무결성을 보호할 수 있다. SQL은 보통 하나의 서버만 사용하는 수직확장으로 문제를 해결해서 NoSQL에 비해 더 좋은 보안 능력을 가지고 있다.
2) DB에 저장하고자 하는 데이터가 구조적이고, 일관적일 때
프로젝트가 많은 서버를 필요로 하지 않고, 데이터가 어느 정도 구조화 되어 있는 경우 보통 RDB를 많이 사용한다. 다양한 데이터 유형과 높은 트래픽 처리를 위한 NoSQL의 장점이 굳이 필요하지 않다.
NoSQL을 사용할 때
1) 데이터의 구조가 거의 또는 전혀 없는 대용량 데이터를 저장할 때
NoSQL은 필요에 따라 데이터에 새 유형을 추가할 수 있기 때문에 정형화 되지 않은 많은 데이터를 저장할 때 사용하면 좋다.
2) 클라우드 컴퓨팅 및 저장공간을 최대한 활용해야 할 때
클라우드 기반으로 DB를 구축하면 이용료가 들긴하지만, DB를 수직확장 해야할 때보다 더 저렴한 방법이 될 수 있다. 수직확장 시에 발생하는 비용과 관리 비용에 비해 번거로움 없이 많은 양의 데이터를 처리 할 수 있다.
3) 빠르게 서비스를 구축하고 데이터 구조를 자주 업데이트 해야할 때
NoSQL은 스키마를 미리 짜놓을 필요가 없기 때문에 개발하는 과정에서 시간이 단축된다. 그리고 데이터 구조를 자주 업데이트 해야한다면 스키마를 일일이 수정해주지 않아도 되어서 관계형 DB보다 더 효율적으로 작업할 수 있다.
NoSQL 종류
위에서 NoSQL DB는 데이터를 key-value, document, wide-column 형태로 저장한다고 했다.
1) Key-Value DB: 파이썬의 Dictionary 처럼 데이터를 key,value로 저장한다. key는 속성의 이름이고, value의 속성의 값이다. Redis, Dynamo가 대표적인 key-value 형식 DB이다.
2) 문서형(Document) DB: 데이터를 테이블이 아닌 문서처럼 저장한다. 보통 JSON과 유사한 형식으로 데이터를 문서화한다. 각각 문서는 하나의 속성에 대한 데이터를 가지고 있고(행) 컬렉션이라고 하는 그룹으로 묶어서 관리한다(행이 여러개가 됨). 대표적인 문서형 DB는 MongoDB가 있다.
3) Wide-Column DB: 데이터베이스의 열에 대한 데이터 관리를 집중적으로 한다. 각 열에는 key-value 형식으로 데이터가 저장되고, column families라고 하는 열의 집합체 단위로 데이터를 처리한다. 하나의 행에 많은 열을 포함할 수 있어서 유연성이 높다. 데이터 처리에 필요한 열을 유연하게 선택할 수 있기 때문에 규모가 큰 데이터 분석에 주로 사용된다. Cassandra, HBase가 대표적인 DB이다.
MongoDB and Pymongo
이미지출처: https://smothly.github.io/data%20engineering/database/2019/12/03/MongoDB-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EB%AA%85%EB%A0%B9%EC%96%B4.html
문서형 DB중 많이 사용되는 MongoDB는 문서들을 BSON(Binary JSON) 형태로 저장한다. NoSQL의 장점답게 데이터의 정해진 틀이나 타입이 없다. 하지만, 위에서도 언급했듯이 어느 정도 스키마가 있어야 데이터를 읽을 때 더 수월하게 가져올 수 있다. MongoDB Atlas로 클러스터를 생성하고, 파이썬의 Pymongo 패키지를 활용하면 파이썬으로 MongoDB에 접근할 수 있다.
- Pymongo 에 데이터 입력
import requests
from pymongo import MongoClient
import json
HOST = '...'
USER = '...'
PASSWORD = '...'
DATABASE = '...'
COLLECTION = '...'
URI = f"mongodb+srv://{USER}:{PASSWORD}@{HOST}/{DATABASE}?retryWrites=true&w=majority"
client = MongoClient(URI) # client 생성
db = client[DATABASE] # DB에 연결
collection = db[COLLECTION] # DB의 collection 연결. SQL로 치면 테이블에 연결.
# 1. API로 가져온 데이터 넣기
response = requests.get("https://api.github.com/users/octokit/repos") # Github API 활용해서 데이터 가져오기
data = json.loads(response.text) # 응답 객체를 text로 변환하고 JSON 형식으로 파싱
collection.insert_many(data) # json형식으로 파싱된 data를 MongoDB Collection에 입력
#insert_many는 다수의 데이터
#insert_one은 한 행의 데이터(단수)
# 2. CSV로 가져온 데이터 넣기
df = pd.read_csv('파일이름.csv')
df = json.loads(df.to_json(orient = 'records')) # df를 json 형태로 바꾸고 json 형식으로 파싱해주어야 한다.
# orient는 어떤 단위로 데이터를 묶을 것인가. records는 행 단위, columns 는 열 단위.
#만약 json.loads를 하지 않으면 형태만 json으로 바뀌고 string 형식으로 저장되어 있기 때문에.
collection.insert_many(df)
- Pymongo를 활용해서 MongoDB에 있는 데이터 조회 및 SQLite에 입력
보통 데이터 엔지니어들은 데이터 수집을 빠르게 하기 위해서 NoSQL로 데이터를 수집한 뒤, 데이터 모델링(스키마 만들기)을 거쳐서 데이터 분석가가 분석이 가능하도록 데이터웨어하우스, 데이터마트 형태로 데이터를 전달 할 수 있다.
데이터웨어하우스(Data Warehouse): 기업에서 사용하고 있는 데이터를 분석에 적합한 정규화 된 모델로 재구성해서 적재해놓은 곳
데이터마트(Data Mart): 데이터웨어하우스의 하위 개념으로 주로 특정 부서나 프로젝트 등의 작은 단위의 분석에 요구할 때 사용하는 데이터를 적재해놓은 곳
보통 Data Warehouse에서 분석에 필요한 정보만을 뽑아서 요약된 데이터로 Data Mart를 구성한다. Data Mart의 장점을 몇가지 설명하자면 불필요하게 많은 양의 데이터를 조회할 필요가 없기 때문에 데이터 조회 성능이 향상된다. 또, Data Warehouse는 정규화까지 구현된 방식으로 설계되어 있어서 쿼리가 복잡한 거에 비해 Data Mart는 쿼리가 덜 복잡하다. Data Warehouse를 구축하는 것보다 비용이나 시간을 더 절약할 수 있다.
데이터 모델링이 완료 됐다면 MongoDB에 적재된 데이터를 JSON 형태로 불러오고, 필요한 데이터를 다시 SQL에 저장할 수 있다.
import sqlite3
# host, password 등의 정보는 위와 같음
# SQLite DB와 필요한 table이 이미 생성되어 있다는 가정
# 1. collection에서 데이터 가져오기
# 전체 데이터가 이런식으로 되어 있다고 가정
# team{ id: 1, coach: Kim, player: { {id : 123, team: MU, name: JS Park} }, records: { { [goals: 15, assists: 5, saves : 0, cards : 3 ] } } }
# 내가 필요한 데이터. 모든 데이터가 필요하지 않다.
# team: {id : 1}
# player: { {id : 123, team: MU, name: JS Park} }
# records: { { [goals: 15, assists: 5] } } # !한번 벗겨내도 리스트로 쌓여져 있다.
# 2. 필요한 변수만 가져오기
# 0이면 가져오지 않고, 1이면 가져옴. 0,1대신 직접 value를 지정해서 가져올 수도 있음.
team_col = {'_id' : 0, 'player' : {'id' : 1, 'team' : 1, 'name' : 1}, 'records' : {'goals' : 1, 'assists' : 1}}
data = collection.find({}, team_col)
# 3. SQLite에 데이터 저장
conn = sqlite3.connect('DB이름')
cur = conn.cursor()
try:
for d in data:
#string은 쌍따옴표로 입력
cur.execute(f'''
INSERT INTO VALUES (
{d['id']},
{d['player']['id']},
"{d['player']['team']}" ,
"{d['player']['name']}",
{d['records'][0]['goals']},
{d['records'][0]['assists']}
);
'''
)
conn.commit()
except:
conn.rollback()
conn.close()
print('데이터 저장에 실패했습니다.')