| 일 | 월 | 화 | 수 | 목 | 금 | 토 | 
|---|---|---|---|---|---|---|
| 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 | 
- 웹해킹
 - 딥러닝
 - 챗GPT
 - BOF
 - Javascript
 - 백엔드
 - Web
 - Linux
 - 러닝스칼라
 - flask
 - c
 - Scala
 - Python
 - 리눅스
 - 파이썬
 - backend
 - php
 - BOF 원정대
 - Shellcode
 - hacking
 - 인공지능
 - 경제
 - 러닝 스칼라
 - deep learning
 - webhacking
 - hackerschool
 - hackthissite
 - ChatGPT
 - mysql
 - c++
 
- 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_nameINSERT
// 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 = value2DELETE
// table_name에 column1이 value1인 row들을 전부 지웁니다. DELETE FROM table_name WHERE column1 = value1JOIN
// 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 |