Skip to content

Commit 037238f

Browse files
authored
Merge pull request #8 from soo-ni/sooni
From sooni to master
2 parents 65746ff + 9b85f64 commit 037238f

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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](https://user-images.githubusercontent.com/19357410/146220631-226f52f8-b269-4f30-8d72-f22da4725fce.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](https://user-images.githubusercontent.com/19357410/146220663-9bddef47-cdd8-4c44-a0d7-8f2107f68fa2.png)
40+
41+
## MySQL 비동기 지원
42+
43+
* X DevAPI
44+
* Kotlin jasync SQL
45+
46+
### X DevAPI
47+
48+
![image](https://user-images.githubusercontent.com/19357410/146220697-ad0e589b-38b3-4be4-bf18-3f83cdd95c4d.png)
49+
50+
* 새로 추가된 부분
51+
52+
* X Protocol
53+
* NoSQL을 몰라도 개발 가능
54+
55+
* 사용
56+
57+
* ```java
58+
import com.mysql.cj.xdevapi.*;
59+
60+
// Connect to server on localhost using a connection URI
61+
Session mySession = new SessionFactory.getSession("mysqlx://localhost:33060/...");
62+
63+
Schema myDb = mySession.getSchema("test");
64+
Table employees = myDb.getTable("employee");
65+
66+
// execute the query asynchronously, obtain a future
67+
CompletableFuture<RowResult> rowsFuture = employees.select("name", "age")
68+
.where("name like: name")
69+
.orderBy("name")
70+
.bind("name". "m?%").executeAsync();
71+
72+
// 비동기 요청 후 필요한 작업 수행
73+
74+
// wait until it's ready
75+
RowResult rows = rowsFuture.get(); // 결과 얻기
76+
77+
```
78+
79+
* getSession => CompletableFuture => executeAsync() => 로직 수행 => rowsFuture.get()
80+
81+
* **ISSUE**
82+
83+
* session이 많아질수록 성능이 떨어짐 (session이 1개일 경우 가장 성능이 좋음)
84+
* ![image](https://user-images.githubusercontent.com/19357410/146220732-85b1ad60-e425-43c2-bee3-f5db4246286b.png)
85+
* 원인 분석
86+
* executeAsync()를 요청할 때 마다 dispatchingThreadMonitor에 lock을 걸고있음
87+
* ListenerDispatcher => while문 마다 lock을 걸고있음
88+
89+
### jasync SQL
90+
91+
* 사용
92+
93+
* ```java
94+
PoolConfiguration poolConfiguration = new PoolConfiguration(
95+
100, // maxObjects
96+
TimeUnit.MINUTES.toMillis(15), // maxIdle
97+
10_000, // maxQueueSize
98+
TimeUnit.SECONDS.toMillis(30) // validationInterval
99+
);
100+
101+
Connection connection = new ConnectionPool<>(
102+
new MySQLConnectionFactory(new Configuration("username", "host.com", 3306, "password", "schema")), poolConfiguration
103+
);
104+
105+
connection.connect().get();
106+
107+
CompletableFuture<QueryResult> future = connection.sendPreparedStatement("select * from table limit 2");
108+
109+
// 비동기 요청 후 필요한 작업을 수행
110+
111+
QueryResult queryResult = future.get();
112+
```
113+
114+
* ConnectionPool => connection.sendPreparedStatement() => 로직 수행 => future.get();
115+
116+
* X DevAPI와 같은 성능문제는 발생하지 않고 있음
117+
118+
### X DevAPI vs. jasync-SQL
119+
120+
![image](https://user-images.githubusercontent.com/19357410/146220764-6057cd31-a4e4-4216-899d-ffe0efa35bbe.png)
121+
122+
* R2DBC 내부적으로 jasync-SQL 사용
123+
124+
## 비동기로 얻을 수 있는 것
125+
126+
* 코드 생산성
127+
* 성능
128+
129+
### 코드 생산성
130+
131+
첫번째 row 출력
132+
133+
#### 동기 방식
134+
135+
```java
136+
Executors.newFixedThreadPool(250).execute(() -> {
137+
SqlSession sqlSession = GameSqlSessionFactory.getSqlSession();
138+
try{
139+
UserDataMapper userDataMapper = sqlSession.getMapper(UserDataMapper.class);
140+
List<Map<String, Object>> userList = userDataMapper.selectUser();
141+
user.forEach(logger::info);
142+
} finally {
143+
sqlSession.close();
144+
}
145+
});
146+
```
147+
148+
#### 비동기 방식
149+
150+
```java
151+
CompletableFuture<QueryResult> future = connection.sendPreparedStatement(sql)
152+
.thenApplyAsync(r -> new ArrayList<>(r.getRows().get(0).columns()))
153+
.thenAccept(r -> r.foreache(logger::info));
154+
```
155+
156+
=> 라이브러리가 대신 처리해주므로 code를 blocking할 필요가 없음
157+
158+
* Redis: jedis (동기) -> lettuce (비동기)
159+
* [참고: Jedis 보다는 Lettuce를 쓰자](https://abbo.tistory.com/107)
160+
161+
### 성능
162+
163+
#### 테스트
164+
165+
![image](https://user-images.githubusercontent.com/19357410/146220791-22504e0e-b25b-46e4-8054-62441031b125.png)
166+
167+
#### Stored Procedure
168+
169+
API방식으로 Transaction을 처리할 때 성능이 낮아지는 문제를 어느정도 줄일 수 있다.
170+
171+
![image](https://user-images.githubusercontent.com/19357410/146220812-12f5ac63-00ea-44ea-856f-744f8215f156.png)
172+
173+
## 정리
174+
175+
* 동기: MyBatis, HIBERNATE
176+
* 비동기: X DevAPI, jasync SQL
177+
178+
179+
180+
* 비동기 방식의 경우 **코드 생산성과 가독성, 성능이 우수**하다.
181+
* API방식으로 Transaction을 처리할 때 성능이 낮아지는 문제를 Stored Procedure에서 Transaction을 처리하는 방식을 통해 성능을 높일 수 있다.
182+
* 그러나, X DevAPI는 세션이 늘어나면 성능이 더 안좋아지는 issue가 있다.
183+
184+
185+
186+
### 개인적으로 더 알아봐야 할 것
187+
188+
* CompletableFuture
189+
* Jedis, Lettuce
190+
* Zeko SQL
191+
* Stored Procedure
192+
* 200만 동접 게임을 위한 MySQL 샤딩 (NHN FORWARD 2019)

0 commit comments

Comments
 (0)