diff --git "a/\354\234\244\354\204\261\355\225\230/[lecture25] db \354\235\270\353\215\261\354\212\244/README.md" "b/\354\234\244\354\204\261\355\225\230/[lecture25] db \354\235\270\353\215\261\354\212\244/README.md" new file mode 100644 index 0000000..e49c03a --- /dev/null +++ "b/\354\234\244\354\204\261\355\225\230/[lecture25] db \354\235\270\353\215\261\354\212\244/README.md" @@ -0,0 +1,154 @@ +# Index가 중요한 이유 + +## 상황 +> 100만개의 데이터가 있을 때 어떠한 값을 찾아야 한다 + +## Index가 없다면? + +full scan(=table scan)으로 찾아야 한다 +시간복잡도 = O(N) + +## Index가 있다면? + +만약 B-Tree 기반의 index라면 +시간복잡도 O(logN)으로 찾을 수 있다. + +Hash index라면? +O(1) ! +하지만 ` = `검색에만 사용 가능하다 (범위조건은 불가능!) +rehashing에 대한 부담도 있다 +composite index의 경우에는 (a, b)로 되어 있을 때 a만 단독 조회가 불가능하다 + +- 쿼리가 더 빠르게 처리된다 = 성능 향상 + +이를 통해 Index를 사용하는 이유를 다음과 같이 확인할 수 있다. +- 조건을 만족하는 튜플을 빠르게 조회 +- 빠르게 정렬(order by) 또는 그룹핑(group by) + +# Index를 생성하는 방법 + +- 이미 테이블이 존재하는 경우 +```mysql +CREATE INDEX index_name ON table_name(column_name); +CREATE UNIQUE INDEX column_1_column_2_idx ON table_name(column_1, column_2); # unique index, composite(multi-column) index +``` + +> [!NOTE] +> +> 관습적으로 인덱스의 이름은 `column 이름`_idx로 사용하는 경우가 많다. +> +> 일반적으로 PK, UNIQUE에는 index가 자동 생성 ([innoDB의 경우 FK도 index가 생긴다](https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html#:~:text=MySQL%20requires%20indexes%20on%20foreign%20keys%20and%20referenced%20keys%20so%20that%20foreign%20key%20checks%20can%20be%20fast%20and%20not%20require%20a%20table%20scan.)) + + +- 테이블을 생성할 때 +```mysql +CREATE TABLE table_name ( + column_1 type constraints, + ..., + INDEX column_n_idx(column_n), # 테이블을 생성하면서 index를 만들 경우 index 이름은 생략 가능 + UNIQUE INDEX column_n_column_m_idx(column_n, column_m) +) +``` + +# 존재하는 Index 확인하는 방법 + +```mysql +SHOW INDEX FROM table_name; +``` + +Seq_in_index를 통해 composite index인지 확인할 수 있다. + +# B Tree 기반의 Index + +![../img/b_tree_index.png](../img/b_tree_index.png) + +이러한 테이블과 데이터가 있을 때 +```mysql +CREATE INDEX(a); +SELECT * WHERE a = 7; +``` +이러한 쿼리를 실행시킨다면 빠르게 인덱스를 사용하여 튜플들을 찾을 수 있다. +![../img/index_a.png](../img/index_a.png) + +```mysql +CREATE INDEX(a); +SELECT * WHERE a = 7 AND b = 95; +``` +하지만 위와 같은 경우는 어떨까? + +a = 7에 대한 튜플들은 빠르게 찾을 수 있지만, b = 95인 조건까지 만족하는지를 일일히 찾아봐야 한다. +```python +for row in tuples: + if row['b'] == 95: + add result +``` + + +그렇기에 WHERE a = 7 AND b = 95인 조건을 만족하는 결과를 빠르게 얻기 위해서는 +```mysql +CREATE INDEX(a, b); +SELECT * WHERE a = 7 AND b = 95; +``` +위와 같이 인덱스를 선언해야 한다. +![../img/index_a_b.png](../img/index_a_b.png) + +이 경우 a에 대한 정렬이 이루어지고 그 안에서 다시 b에 대한 정렬이 이루어진다. +```python +datas.sort(key=lambda x:(x['a'], x['b'])) +``` +그렇기에 +```mysql +CREATE INDEX(a, b); +SELECT * WHERE b = 95; +``` +위와 같이 b에 대한 검색만 수행해야 하는 경우 인덱스를 사용하기 어렵다 (사용해도 오히려 full scan보다 느릴 수 있다) +이 경우 b에 대한 index 추가 필요 + +## 연습문제 + +```mysql +CREATE INDEX(a, b); + +SELECT * FROM mytable WHERE a = 1; + +SELECT * FROM mytable WHERE a = 1 AND b = 2; + +SELECT * FROM mytable WHERE b = 2; + +SELECT * FROM mytable WHERE a = 1 OR b = 2; +``` + +4개의 쿼리 중 어떤 쿼리에 인덱스를 사용한 효과가 좋을까? + +1: O, 2: O, 3: X, 4: X +4는 OR 조건이기 때문에 b = 2에 대한 full scan이 필요하다 + +# Index 명시적으로 사용하기 + +```mysql +// USE INDEX는 가급적 해당 인덱스를 사용하라고 부탁 +SELECT * FROM table_name USE INDEX (index_name) WHERE ...; + +// FORCE INDEX는 5살짜리가 봐도 아닌것같다 싶은게 아니라면 해당 인덱스를 사용하라고 강요 +SELECT * FROM table_name FORCE INDEX (index_name) WHERE ...; + +# IGNORE INDEX로 사용하지 않을 인덱스를 설정할 수 도 있다. +``` + +# Index를 많이 생성하였을 때의 문제점 + +- table에 write 할 때마다 index에도 변경이 발생 +- 추가적인 저장 공간 차지 + +결론: read 속도는 빨라지지만 CUD의 속도는 느려질 수 있다. +그러므로 꼭 필요한 인덱스만 만들자 + +# Index를 사용하면 무조건 더 빠를까? + +- table에 데이터가 조금 있을 때 +- 조회하려는 데이터가 테이블의 상당 부분을 차지할 때(카디널리티가 낮을때) + +# Covering Index + +- 조회하는 attribute를 index만으로 cover될 때 +- 실제 데이터에 접근을 하지 않아도 되므로 조회가 빠르다 \ No newline at end of file diff --git "a/\354\234\244\354\204\261\355\225\230/[lecture28] B tree/README.md" "b/\354\234\244\354\204\261\355\225\230/[lecture28] B tree/README.md" new file mode 100644 index 0000000..3402df1 --- /dev/null +++ "b/\354\234\244\354\204\261\355\225\230/[lecture28] B tree/README.md" @@ -0,0 +1,27 @@ +# 왜 B tree인가? + +![../img/btree_index.png](../img/btree_index.png) + +위와 같이 B tree와 self balancing BST(SBBST) 모두 시간복잡도가 동일한 것을 알 수 있다. +그렇다면 왜 SBBST를 인덱스로 사용하지 않을까? + +> 결론은 같은 logN이더라도 SBBST는 log2N이지만 B tree는 log5N과 같이 로그의 밑이 더 클 수 있다 → 더 적은 횟수의 second storage 접근으로 데이터 조회가 가능하다! + +## 들어가기에 앞서 + +Main Memory(RAM) +- 코드 실행에 필요한 데이터들이 상주하는 공간 +- 40 ~ 50 GB/s + +Second Storage(SSD or HDD) +- 프로그램과 데이터가 영구적으로 저장되는 공간 +- 실행중인 프로그램의 데이터 일부가 임시 저장되는 공간(swap 공간) +- 3 ~ 5 GB/s (SSD), 0.2 ~ 0.3 GB/s (HDD) +- 데이터 처리 속도가 느리다 +- 데이터를 block단위로 읽고 쓴다 + +데이터를 block단위로 처리하기 때문에 연관된 데이터를 모아서 저장하면 효율적이다 (더 적게 디스크에 접근해도 데이터를 처리할 수 있기 때문에) + +# B Tree vs AVL tree 비교 + +![../img/b_tree_vs_avl_tree.png](../img/b_tree_vs_avl_tree.png) \ No newline at end of file diff --git "a/\354\234\244\354\204\261\355\225\230/[lecture29] partitioning, sharding, replication/README.md" "b/\354\234\244\354\204\261\355\225\230/[lecture29] partitioning, sharding, replication/README.md" new file mode 100644 index 0000000..9d31691 --- /dev/null +++ "b/\354\234\244\354\204\261\355\225\230/[lecture29] partitioning, sharding, replication/README.md" @@ -0,0 +1,80 @@ +# Partitioning + +## Vertical Partitioning +> column을 기준으로 table을 나누는 방식 + +정규화도 일종의 vertical partitioning! + +게시판에서 vertical partitioning 적용하기 +- 게시글 목록을 볼 때는 일반적으로 게시글의 내용은 가져오지 않는다 +- 하지만 select 쿼리는 row의 전체 데이터를 가져오기 때문에 게시글에 대한 내용이 불필요해도 데이터를 읽어온다 +- 게시글의 크기는 다른 column에 비해 클 가능성이 높은 편 +- 앞에서 배웠듯이 2차 저장소는 데이터를 block단위로 처리하기 때문에 연관된 데이터를 모아서 저장해야 접근 횟수를 줄여 효율적 +- 그렇기에 게시글에 대한 내용을 분리하면 full scan시 효율적이다 + +Vertical Partitioning을 하는 목적 +- 퍼포먼스 +- 민감한 정보 제한 +- 자주 사용되는 column, 자주 사용되지 않는 column 분리 + +## Horizontal Partitioning +> row를 기준으로 table을 나누는 방식 + +Horizontal Partitioning을 하는 이유 +- 테이블의 크기가 커질수록 인덱스의 크기도 커진다 +- 테이블에 읽기/쓰기 처리 시간도 늘어난다 +- 이를 해결하기 위해 row를 기준으로 테이블을 분리하여 테이블 당 데이터 수를 줄인다 + +Horizontal Partitioning 방법 +- hash +- range +- …etc +## Hash-based Horizontal Partitioning + +해시 함수를 사용하여 파티셔닝 +![../img/hash_horizontal_partitioning.png](../img/hash_horizontal_partitioning.png) + +case 1 +- 특정 유저가 구독한 채널 ID를 얻기 +- subscription_${hash(user_id)}의 테이블에서 정보를 가져오면 된다 +case 2 +- 채널 id가 1인 채널을 구독한 유저 id를 얻기 +- 파티셔닝이 user_id를 기준으로 이루어졌으므로 모든 테이블을 찾아봐야 한다 + +따라서, 가장 많이 사용될 패턴에 따라 partition key를 정하는 것이 중요하다. +데이터가 균등하게 분배되어야 horizontal partitioning의 이점을 살릴 수 있다. +한번 파티셔닝이 이루어졌으면 이후 파티션의 수를 바꾸기 힘들다(rehashing 필요) + + +# Sharding +> horizontal partitioning과 유사하지만 sharding은 테이블이 다른 DB에 저장 + +부하 분산이 주된 목적이다 (Horizontal partitioning은 하나의 DB 서버에 모든 트래픽이 몰림) + +Horizontal Partitioning과 용어 차이 +- partition key → shard key +- partition → shard + +# Replication +> 데이터를 여러 서버에 걸쳐 복제 + +Primary Server +- 데이터 원본 저장, 데이터 변경 작업 +Secondary Server +- Primary Server의 복제본, 일반적으로는 읽기 전용 + +- 한 서버에 문제가 생겨도 복제된 DB 서버를 사용할 수 있어 빠른 장애 극복이 가능하다 (Fail over, HA) +- 대부분의 트래픽은 Read이기에 primary에만 write, read는 분산하는 방식으로 부하를 나눌 수 있다 + +> [!NOTE] +> +> ### Replication 동작 원리 +> +> 1. **바이너리 로그(Binary Log) 생성**: + > - Primary 서버는 모든 데이터 변경 작업을 바이너리 로그에 기록합니다. 이 로그는 시퀀스 번호와 타임스탬프를 포함하여 각 데이터 변경 작업을 기록합니다. +> 1. **로그 전송**: +> - Secondary 서버는 Primary 서버의 바이너리 로그를 주기적으로 확인하고, 새로운 변경 사항이 있는 경우 이를 가져옵니다. +> 1. **릴레이 로그(Relay Log) 기록**: +> - Secondary 서버는 Primary 서버에서 가져온 바이너리 로그를 자신의 릴레이 로그에 저장합니다. +> 1. **릴레이 로그 적용**: +> - Secondary 서버는 릴레이 로그에 기록된 데이터 변경 사항을 순차적으로 실행하여 자신의 데이터베이스에 반영합니다. diff --git "a/\354\234\244\354\204\261\355\225\230/[lecture30] dbcp/README.md" "b/\354\234\244\354\204\261\355\225\230/[lecture30] dbcp/README.md" new file mode 100644 index 0000000..872d829 --- /dev/null +++ "b/\354\234\244\354\204\261\355\225\230/[lecture30] dbcp/README.md" @@ -0,0 +1,35 @@ +# DBCP + +매 요청 시 connection을 열고 닫는것은 성능에 좋지 않다 (3 way handshake) + +그래서 나온게 Connection Pool +처음에 커넥션을 미리 만들어두었다가 요청 시 커넥션 풀에서 커넥션을 가져와 사용 +→ 커넥션을 재사용하기때문에 열고 닫는 시간을 절약할 수 있다 + +# DB 서버 설정(Mysql 기준) + +- max_connections: client와 맺을 수 있는 최대 커넥션 수 +- wait_timeout: connection이 inactive 할 때 다시 요청이 오기까지 얼마의 시간을 기다린 뒤에 close 할 것인지 + +# DBCP 설정 (HikariCP 기준) + +- minimulIdle: pool에서 유지하는 최소한의 idle connection 수 +- maximumPoolSize: pool이 가질 수 있는 최대 connection 수 + - idle과 in-use connection 합친 최대 수 + - 권장은 minimumIdle == maximumPoolSize (pool size 고정) +- maxLifetime: pool에서 connection의 최대 수명 + - maxLifetime을 넘기면 idle인 경우 바로 제거, in-use인 경우 pool로 반환 후 제거 + - **pool로 반환이 안되면 maxLifetime이 동작을 하지 않는다!** + - DB의 connection time limit보다 몇 초 짧게 설정해야 한다 + - 만약 동일하다면 요청이 날아가는 중에 DB에서 커넥션을 끊어버릴 수 도 있다 +- connectionTimeout: pool에서 connection을 받기 위한 대기 시간 + - 시간을 길게 잡아도 과연 그 시간동안 사용자가 기다려줄까? + +# 적절한 Connection 수를 찾아보기 + +부하테스트! + +- 백엔드 서버, DB 서버의 CPU, MEM 등등 리소스 사용률 확인 +- thread per request 모델이라면 active thread 수 확인 +- DBCP의 active connection 수 확인 +- 사용할 백엔드 서버 수를 고려하여 DBCP의 max pool size 결정 \ No newline at end of file diff --git "a/\354\234\244\354\204\261\355\225\230/[lecture31] NoSQL/README.md" "b/\354\234\244\354\204\261\355\225\230/[lecture31] NoSQL/README.md" new file mode 100644 index 0000000..dea63b5 --- /dev/null +++ "b/\354\234\244\354\204\261\355\225\230/[lecture31] NoSQL/README.md" @@ -0,0 +1,23 @@ +# RDB의 단점 + +- 유연한 확정성의 부족 +- 중복 제거를 위해 정규화 진행 + - 여러 join을 해야 하는 경우도 있다 → 성능 하락 +- scale-out이 어렵다 (서비스 중인데 scale out을 하려면 데이터를 전부 복제해야하니 부담) +- ACID를 보장하려다 보니 퍼포먼스에 영향이 있다 + +# NoSQL 등장 배경 + +- high-throughput 요구 +- low-latency 요구 +- 비정형 데이터의 증가 + +# NoSQL의 특징 + +- 유연한 스키마 → application 레벨에서 스키마 관리가 필요 +- 중복 허용 (join 회피) → application 레벨에서 중복된 데이터들이 최신 데이터로 유지되도록 관리 필요 +- scale-out이 쉽다 → 일반적으로 여러 서버들로 클러스터를 구성하여 사용한다 + - join할 필요 없이 데이터를 읽어오면 되니까 데이터를 나눠서 저장해도 문제가 적다 +- ACID의 일부를 포기 + - high-thoughput, low-latency 추구 + diff --git "a/\354\234\244\354\204\261\355\225\230/img/b_tree_index.png" "b/\354\234\244\354\204\261\355\225\230/img/b_tree_index.png" new file mode 100644 index 0000000..cad4d7a Binary files /dev/null and "b/\354\234\244\354\204\261\355\225\230/img/b_tree_index.png" differ diff --git "a/\354\234\244\354\204\261\355\225\230/img/b_tree_vs_avl_tree.png" "b/\354\234\244\354\204\261\355\225\230/img/b_tree_vs_avl_tree.png" new file mode 100644 index 0000000..bce88e9 Binary files /dev/null and "b/\354\234\244\354\204\261\355\225\230/img/b_tree_vs_avl_tree.png" differ diff --git "a/\354\234\244\354\204\261\355\225\230/img/btree_index.png" "b/\354\234\244\354\204\261\355\225\230/img/btree_index.png" new file mode 100644 index 0000000..a51d6d2 Binary files /dev/null and "b/\354\234\244\354\204\261\355\225\230/img/btree_index.png" differ diff --git "a/\354\234\244\354\204\261\355\225\230/img/hash_horizontal_partitioning.png" "b/\354\234\244\354\204\261\355\225\230/img/hash_horizontal_partitioning.png" new file mode 100644 index 0000000..b3795c4 Binary files /dev/null and "b/\354\234\244\354\204\261\355\225\230/img/hash_horizontal_partitioning.png" differ diff --git "a/\354\234\244\354\204\261\355\225\230/img/index_a.png" "b/\354\234\244\354\204\261\355\225\230/img/index_a.png" new file mode 100644 index 0000000..f10bdba Binary files /dev/null and "b/\354\234\244\354\204\261\355\225\230/img/index_a.png" differ diff --git "a/\354\234\244\354\204\261\355\225\230/img/index_a_b.png" "b/\354\234\244\354\204\261\355\225\230/img/index_a_b.png" new file mode 100644 index 0000000..f0f81f5 Binary files /dev/null and "b/\354\234\244\354\204\261\355\225\230/img/index_a_b.png" differ