Skip to content

Commit 17bffdd

Browse files
authored
[ADD] [DB] [NHN FORWARD 2021] 비동기 쿼리
1 parent e4626aa commit 17bffdd

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# [NHN FORWARD 2021] Java에서 MySQL 비동기 쿼리 사용하기
2+
3+
* 2021.12.14 NHN FORWARD `Java에서 MySQL 비동기 쿼리 사용하기` session 정리
4+
* https://forward.nhn.com/2021/sessions/11
5+
6+
## 전통적인 동기 방식의 쿼리와 문제
7+
8+
* 쿼리 요청 (Thread) => 쿼리 작업 (DB) => 결과 응답
9+
* ![image-20211215233703478](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211215233703478.png)
10+
* 쿼리 작업 시 **응답이 올 때까지 대기**해야하는 문제 발생
11+
* 이를 위해서 `Connection Pool`, `Thread Pool`이 사용되었다.
12+
13+
### Connection Pool & Thread Pool
14+
15+
* 미리 DB와 Connection을 연결해 둔 다음
16+
* 요청이 오는 경우 쿼리 작업 후 결과 응답
17+
18+
**BUT**
19+
20+
* 커넥션 풀, 스레드 풀의 사이즈를 적절하게 사용해야하는 문제
21+
* 동시 접속자가 많을 경우 connection이 없을 경우 반환될 때까지 대기
22+
* 서버가 여유가 있어도 데이터베이스 병목(처리 대기 시간)으로 인해 서버를 늘리게 되는 경우 발생
23+
24+
25+
26+
* [참고: Spring-3-커넥션-풀이란](https://linked2ev.github.io/spring/2019/08/14/Spring-3-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80%EC%9D%B4%EB%9E%80/)
27+
* [참고: DB Connection Pool](https://hyuntaeknote.tistory.com/12)
28+
29+
* [참고: Thread Pool(스레드 풀) 이란](https://limkydev.tistory.com/55)
30+
* [참고: MySQL을 시작하기 전에 3](https://dung-beetle.tistory.com/68)
31+
32+
## 비동기 처리
33+
34+
* 동기 방식의 문제를 처리하기 위해 비동기 처리 방식을 사용
35+
36+
### 비동기 처리?
37+
38+
* 응답을 대기하지 않고 계속적으로 처리
39+
* ![image-20211215233945745](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211215233945745.png)
40+
41+
## MySQL 비동기 지원
42+
43+
* X DevAPI
44+
* Kotlin jasync SQL
45+
46+
### X DevAPI
47+
48+
![image-20211216003211953](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211216003211953.png)
49+
50+
* 새로 추가된 부분
51+
* X Protocol
52+
* NoSQL을 몰라도 개발 가능
53+
54+
* 사용
55+
56+
* ```java
57+
import com.mysql.cj.xdevapi.*;
58+
59+
// Connect to server on localhost using a connection URI
60+
Session mySession = new SessionFactory.getSession("mysqlx://localhost:33060/...");
61+
62+
Schema myDb = mySession.getSchema("test");
63+
Table employees = myDb.getTable("employee");
64+
65+
// execute the query asynchronously, obtain a future
66+
CompletableFuture<RowResult> rowsFuture = employees.select("name", "age")
67+
.where("name like: name")
68+
.orderBy("name")
69+
.bind("name". "m?%").executeAsync();
70+
71+
// 비동기 요청 후 필요한 작업 수행
72+
73+
// wait until it's ready
74+
RowResult rows = rowsFuture.get(); // 결과 얻기
75+
76+
```
77+
78+
* getSession => CompletableFuture => executeAsync() => 로직 수행 => rowsFuture.get()
79+
80+
* **ISSUE**
81+
* session이 많아질수록 성능이 떨어짐 (session이 1개일 경우 가장 성능이 좋음)
82+
* ![image-20211215234437651](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211215234437651.png)
83+
* 원인 분석
84+
* executeAsync()를 요청할 때 마다 dispatchingThreadMonitor에 lock을 걸고있음
85+
* ListenerDispatcher => while문 마다 lock을 걸고있음
86+
87+
### jasync SQL
88+
89+
* 사용
90+
91+
* ```java
92+
PoolConfiguration poolConfiguration = new PoolConfiguration(
93+
100, // maxObjects
94+
TimeUnit.MINUTES.toMillis(15), // maxIdle
95+
10_000, // maxQueueSize
96+
TimeUnit.SECONDS.toMillis(30) // validationInterval
97+
);
98+
99+
Connection connection = new ConnectionPool<>(
100+
new MySQLConnectionFactory(new Configuration("username", "host.com", 3306, "password", "schema")), poolConfiguration
101+
);
102+
103+
connection.connect().get();
104+
105+
CompletableFuture<QueryResult> future = connection.sendPreparedStatement("select * from table limit 2");
106+
107+
// 비동기 요청 후 필요한 작업을 수행
108+
109+
QueryResult queryResult = future.get();
110+
```
111+
112+
* ConnectionPool => connection.sendPreparedStatement() => 로직 수행 => future.get();
113+
114+
* X DevAPI와 같은 성능문제는 발생하지 않고 있음
115+
116+
### X DevAPI vs. jasync-SQL
117+
118+
![image-20211215234934537](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211215234934537.png)
119+
120+
* R2DBC 내부적으로 jasync-SQL 사용
121+
122+
## 비동기로 얻을 수 있는 것
123+
124+
* 코드 생산성
125+
* 성능
126+
127+
### 코드 생산성
128+
129+
첫번째 row 출력
130+
131+
#### 동기 방식
132+
133+
```java
134+
Executors.newFixedThreadPool(250).execute(() -> {
135+
SqlSession sqlSession = GameSqlSessionFactory.getSqlSession();
136+
try{
137+
UserDataMapper userDataMapper = sqlSession.getMapper(UserDataMapper.class);
138+
List<Map<String, Object>> userList = userDataMapper.selectUser();
139+
user.forEach(logger::info);
140+
} finally {
141+
sqlSession.close();
142+
}
143+
});
144+
```
145+
146+
#### 비동기 방식
147+
148+
```java
149+
CompletableFuture<QueryResult> future = connection.sendPreparedStatement(sql)
150+
.thenApplyAsync(r -> new ArrayList<>(r.getRows().get(0).columns()))
151+
.thenAccept(r -> r.foreache(logger::info));
152+
```
153+
154+
=> 라이브러리가 대신 처리해주므로 code를 blocking할 필요가 없음
155+
156+
* Redis: jedis (동기) -> lettuce (비동기)
157+
* [참고: Jedis 보다는 Lettuce를 쓰자](https://abbo.tistory.com/107)
158+
159+
### 성능
160+
161+
#### 테스트
162+
163+
![image-20211215235500538](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211215235500538.png)
164+
165+
#### Stored Procedure
166+
167+
API방식으로 Transaction을 처리할 때 성능이 낮아지는 문제를 어느정도 줄일 수 있다.
168+
169+
![image-20211215235525361](C:\Users\ksb94\AppData\Roaming\Typora\typora-user-images\image-20211215235525361.png)
170+
171+
## 정리
172+
173+
* 동기: MyBatis, HIBERNATE
174+
* 비동기: X DevAPI, jasync SQL
175+
176+
177+
178+
* 비동기 방식의 경우 **코드 생산성과 가독성, 성능이 우수**하다.
179+
* API방식으로 Transaction을 처리할 때 성능이 낮아지는 문제를 Stored Procedure에서 Transaction을 처리하는 방식을 통해 성능을 높일 수 있다.
180+
* 그러나, X DevAPI는 세션이 늘어나면 성능이 더 안좋아지는 issue가 있다.
181+
182+
183+
184+
### 개인적으로 더 알아봐야 할 것
185+
186+
* CompletableFuture
187+
* Jedis, Lettuce
188+
* Zeko SQL
189+
* Stored Procedure
190+
* 200만 동접 게임을 위한 MySQL 샤딩 (NHN FORWARD 2019)

0 commit comments

Comments
 (0)