일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- php
- hacking
- BOF
- ChatGPT
- Scala
- Linux
- Python
- hackerschool
- 러닝스칼라
- 딥러닝
- Shellcode
- Web
- flask
- 리눅스
- Javascript
- 웹해킹
- BOF 원정대
- 경제
- 백엔드
- c
- 파이썬
- deep learning
- hackthissite
- 인공지능
- backend
- webhacking
- 챗GPT
- c++
- mysql
- 러닝 스칼라
- Today
- Total
jam 블로그
깔끔한 파이썬 탄탄한 백엔드 -6- 본문
6. 데이터 베이스
데이터를 저장 및 보존하는 시스템
데이터 베이스의 종류
관계형 데이터 베이스 시스템 (RDBMS, Relational Database Management System)
- 데이터들이 서로 상호관련성을 가진 형태로 표현한 데이터
- 대표적으로 MySQL, PostgresSQL
- 모든 데이터는 2차원 테이블로 표현
- 각 테이블은 칼럼(column), 로우(row)로 구성
- 각 로우는 고유 키(primary key)가 존재
- 테이블들의 상호 관련성 종류
- one to one
- A테이블의 로우와 B테이블의 로우가 정확히 일대일 매칭일 경우
- 예시 : 국가에 수도 연결
- A테이블의 로우와 B테이블의 로우가 정확히 일대일 매칭일 경우
- one to many
- 테이블 A의 로우가 테이블 B의 여러 로우와 연결되는 관계
- 예시 : 한 유저가 여러 개의 게시물을 쓸수 있음
- 테이블 A의 로우가 테이블 B의 여러 로우와 연결되는 관계
- many to many
- 테이블 A의 여러 로우가 테이블 B의 여러 로우와 연결되는 경우
- 예시 : 한 유저를 여러 사용자가 팔로우 할수 있고 해당 사용자 또한 여러 사용자를 팔로우 할수 있는 경우
- 테이블 A의 여러 로우가 테이블 B의 여러 로우와 연결되는 경우
- one to one
- 트랜젝션
- 모든 루틴이 정상적으로 실행되었을때만 데이터베이스에 영구적으로 반영되며, 그게 아닐 경우 이전 상태로 복구
- ACID
- Atomicity(원자성) : 모든 작업이 반영되거나 모두 롤백
- Consistenct(일관성) : 데이터는 미리 정의된 규칙에서만 수정이 가능
- Isolation(고립성) : A와 B 두개의 트랜젝션이 실행되고 있을 때 A의 작업들이 B에게 보여지는 정도
- Durability(영구성) : 한번 커밋된 트랜젝션의 내용은 영원히 적용되는 특성을 의미
- 장점
- 데이터를 더 효율적이고 체계적으로 저장 관리가 가능
- 데이터들의 구조를 미리 정의함으로써 데이터의 완전성이 보장
- 트랜잭션 기능을 제공
- 단점
- 데이블 구조 변화등에 덜 유연하다
- 확장이 쉽지 않다
- 서버를 늘려 분산 저장이 쉽지 않다. 스케일 아웃(서버 수 늘리기)보다는 서케일 업(서버 성능 업)을 해야한다.
비관계형 데이터 베이스 시스템 (NoSQL, Non-relational Database Mangement System)
테이블들의 스키마(schema)와 테이블들의 관계를 미리 구현해야 하는 필요가 없이 데이터가 들어오는 그대로 저장
저장되는 데이터의 구조에 따라 달라집니다.
Key Value DB : Key와 Value의 쌍으로 데이터가 저장되는 가장 단순한 형태의 솔루션
Wide Columnar Store : Big Table DB라고하며, Key, Value에서 발전된 형태로 Column Family 데이터 모델을 사용
Document DB : JSON, XML과 같은 Collection 데이터 모델 구조를 채택
Graph DB : Nodes, Relationship, Key-Value 데이터 모델을 채용장점
- 저장하는 데이터의 구조 변화에 유연하다.
- 시스템 확장하기 쉬워 스케일 아웃 방식이 가능하다.
- 방대한 양의 데이터를 저장하는데 유리
단점
- 데이터의 완전성이 덜 보장된다.
- 트랜젝션이 안되거나 되더라도 불안하다.
SQL
Structured Query Language이며, 관계현 데이터베이스에서 데이터를 읽거나 생성 및 수정하기 위해 사용되는 언어이며, 아래는 Mysql에서 기본적인 CRUD(Create, Read, Update, Delete)를 위한 기본적인 Query 명령어입니다. Query에서 column이나 table이름같은 고유한 값을 제외한 명령어들은 대/소문자 상관 없이 쓸수 있습니다.
SELECT
// table_name에서 column1, column2를 가져옵니다. SELECT column1, column2 from table_name // table_name에서 모든(*) column을 가져옵니다. SELECT * from table_name
INSERT
// table_name에 column1에 column1_value를 column2에 column2_value를 넣습니다. INSERT INTO table_name(column1, column2) VALUES (column1_value, column2_value)
UPDATE
// table_name에 column2가 value2인(WHERE 뒤가 조건문) row들에서 column1에 value1으로 update합니다. UPDATE table_name SET column1 = value1 WHERE column2 = value2
DELETE
// table_name에 column1이 value1인 row들을 전부 지웁니다. DELETE FROM table_name WHERE column1 = value1
JOIN
// table2에서 tabel1의 id와 table2에서 table1_id가 같은 값을 table1에 붙인 후 table1.column1과 table2.column2를 출력합니다. SELECT table1.column1, table2.column2 FROM table1 JOIN table2 ON table1.id = table2.table1_id
Mysql 설정
sudo apt update
sudo apt install mariadb-server
# 보안상 해야하면 remove 할 것들은 전부 remove하세요
$> mysql_secure_installation
# 만약 remote에서 mysql을 접속하고 싶을때
# 파일에서 bind_address=127.0.0.1을 0.0.0.0으로 변경합니다.
sudo vi /etc/mysql/my.cnf
sudo service mysql restart
# 보안상 root는 remote를 못하게 막았기 때문에 원격으로 붙을 수 있는 계정을 생성해야합니다.
$> mysql -u root -p
# 사용할 스키마명 생성
mysql> CREATE database 스키마명;
# 생성한 스키마를 사용할 유저 생성
mysql> CREATE user '계정아이디'@localhost identified by '비밀번호';
mysql> CREATE user '계정아이디'@'%' identified by '비밀번호';
mysql> GRANT all privileges on '스키마명'.* to '계정아이디'@'%' identified by '비밀번호' with grant option;
mysql> flush privileges;
API와 데이터 베이스 연결하기
miniter에서 사용할 DB를 만들어 보겠습니다. 위에서 스키마명을 miniter로 하고 진행하면 됩니다.
mysql> use miniter;
mysql> CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(80) NOT NULL,
hashed_password VARCHAR(255) NOT NULL,
profile VARCHAR(2000) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY email (email)
) DEFAULT CHARSET=utf8mb4;
mysql> CREATE TABLE users_follow_list (
user_id INT NOT NULL,
follow_user_id INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, follow_user_id),
CONSTRAINT users_follow_list_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id),
CONSTRAINT users_follow_list_follow_user_id_fkey FOREIGN KEY (follow_user_id) REFERENCES users(id)
) DEFAULT CHARSET=utf8mb4;
mysql> CREATE TABLE tweets(
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
tweet VARCHAR(300) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT tweets_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id)
)DEFAULT CHARSET=utf8mb4;
# 이미 테이블을 만들어서 charset을 바꾸고 싶다면 다음과 같이 쓰면됩니다.
mysql> ALTER TABLE users convert to CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
mysql> ALTER TABLE users_follow_list convert to CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
mysql> ALTER TABLE tweets convert to CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
# 위와 같이 할 때 Specified key was too long; max key length is 767 bytes 이러한 에러가 날 경우 다음과 같이 변경합니다.
# users에 email VARCHAR(255) NOT NULL를 email VARCHAR(80) NOT NULL 로 변경합니다.
- 아래는 책과 다르게 파일들을 분할하여 작성한 Full Source 입니다.
# config.py
# user, password는 DB의 id와 password 입니다.
db = {
'user': '',
'password': '',
'host': 'localhost',
'port': 3306,
'database': 'miniter'
}
DB_URL = f"mysql+mysqlconnector://{db['user']}:{db['password']}@{db['host']}:{db['port']}/{db['database']}?charset=utf8"
# util.py
from flask.json import JSONEncoder
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return JSONEncoder.default(self, obj)
# api.py
from flask import current_app
from sqlalchemy import text
def get_user(user_id):
user = current_app.database.execute(text("""
SELECT
id, name, email, profile
FROM
users
WHERE id = :user_id
"""), {'user_id': user_id}).fetchone()
return {
'id' : user['id'],
'name' : user['name'],
'email' : user['email'],
'profile' : user['profile']
} if user else None
def insert_user(user):
return current_app.database.execute(text("""
INSERT INTO users (
name,
email,
profile,
hashed_password
) VALUES (
:name,
:email,
:profile,
:password
)
"""), user).lastrowid
def insert_tweet(user_tweet):
return current_app.database.execute(text("""
INSERT INTO tweets (
user_id,
tweet
) VALUES (
:id,
:tweet
)
"""), user_tweet).rowcount
def insert_follow(user_follow):
return current_app.database.execute(text("""
INSERT INTO users_follow_list (
user_id,
follow_user_id
) VALUES (
:id,
:follow
)
"""), user_follow).rowcount
def insert_unfollow(user_unfollow):
return current_app.database.execute(text("""
DELETE FROM user_follow_list
WHERE user_id = :id
AND follow_user_id = :unfollow
"""), user_unfollow).rowcount
def get_timeline(user_id):
timeline = current_app.database.execute(text("""
SELECT
t.user_id,
t.tweet
FROM tweets t
LEFT JOIN users_follow_list ufl ON ufl.user_id = :user_id
WHERE t.user_id = :user_id OR t.user_id = ufl.follow_user_id
"""),{
"user_id": user_id
}).fetchall()
return [{
'user_id': tweet['user_id'],
'tweet': tweet['tweet']
} for tweet in timeline]
# app.py
from flask import Flask, jsonify, request
from sqlalchemy import create_engine
from util import *
from api import *
def create_app(test_config=None):
app = Flask(__name__)
app.json_encoder = CustomJSONEncoder
if test_config is None:
app.config.from_pyfile("config.py")
else:
app.config.update(test_config)
database = create_engine(app.config['DB_URL'], encoding='utf-8', max_overflow = 0)
app.database = database
@app.route('/', methods=['GET'])
def index():
return "Hello Flask"
@app.route('/ping', methods=['GET'])
def ping():
return "pong"
@app.route("/sign-up", methods=['POST'])
def sign_up():
new_user = request.json
new_user_id = insert_user(new_user)
new_user = get_user(new_user_id)
return jsonify(new_user)
@app.route('/tweet', methods=['POST'])
def tweet():
user_tweet = request.json
tweet = user_tweet['tweet']
if len(tweet) > 300:
return '300자를 초과했습니다.', 400
insert_tweet(user_tweet)
return '', 200
@app.route('/follow', methods=['POST'])
def follow():
payload = request.json
insert_follow(payload)
return '', 200
@app.route('/unfollow', methods=['POST'])
def unfollow():
payload = request.json
insert_unfollow(payload)
return '', 200
@app.route('/timeline/<int:user_id>', methods=['GET'])
def timeline(user_id):
return jsonify({
'user_id': user_id,
'timeline': get_timeline(user_id)
})
return app
if __name__ == '__main__':
app = create_app()
app.run(host='0.0.0.0', port=5000, debug=True)
- 기존과 동일하게 python app.py로 실행 후 postman으로 api를 쏴보면 제대로 동작하며 DB에 값이 쌓이는것을 확인 할 수 있습니다.
'IT Book Study > 깔끔한 파이썬 탄탄한 백엔드' 카테고리의 다른 글
깔끔한 파이썬 탄탄한 백엔드 -8- (0) | 2020.09.03 |
---|---|
깔끔한 파이썬 탄탄한 백엔드 -7- (0) | 2020.09.03 |
깔끔한 파이썬 탄탄한 백엔드 -5- (0) | 2020.09.03 |
깔끔한 파이썬 탄탄한 백엔드 -4- (0) | 2020.09.03 |
깔끔한 파이썬 탄탄한 백엔드 -3- (0) | 2020.09.03 |