Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions 윤성하/[lecture25] db 인덱스/README.md
Original file line number Diff line number Diff line change
@@ -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될 때
- 실제 데이터에 접근을 하지 않아도 되므로 조회가 빠르다
27 changes: 27 additions & 0 deletions 윤성하/[lecture28] B tree/README.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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 서버는 릴레이 로그에 기록된 데이터 변경 사항을 순차적으로 실행하여 자신의 데이터베이스에 반영합니다.
35 changes: 35 additions & 0 deletions 윤성하/[lecture30] dbcp/README.md
Original file line number Diff line number Diff line change
@@ -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 결정
23 changes: 23 additions & 0 deletions 윤성하/[lecture31] NoSQL/README.md
Original file line number Diff line number Diff line change
@@ -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 추구

Binary file added 윤성하/img/b_tree_index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 윤성하/img/b_tree_vs_avl_tree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 윤성하/img/btree_index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 윤성하/img/hash_horizontal_partitioning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 윤성하/img/index_a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 윤성하/img/index_a_b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.