Spring Session JDBC ๋์ ๊ธฐ
๐ฌ Intro
๋ถ์ฐ ํ๊ฒฝ์์ Session ๊ด๋ฆฌ๋ฅผ ์ํ Spring Session JDBC๋ฅผ ๋์ ํ๋ ๊ณผ์ ์ ์๊ฐํฉ๋๋ค.
๊ฒฐ๋ก
Spring Session JDBC๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ์ฐํ๊ฒฝ์์ Session ๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ๊ณ , EC2 ์ถ๊ฐ ์ธํ๋ผ ๋น์ฉ ์ ์ฝ 20% ์ ๊ฐ ํ์์ต๋๋ค.
ํ์ฌ ์ํฉ
- ๋จ์ผ ์๋ฒ ํ๊ฒฝ์์ ๋ถ์ฐ ํ๊ฒฝ์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์
- Session์ด ์๋ฒ ๋ฉ๋ชจ๋ฆฌ์์ ๊ด๋ฆฌ
ํ์ฌ ์ํฉ์ด ์ ๋ฌธ์ ๊ฐ ๋๋๊ฐ?
์๋ฒ ๋ฉ๋ชจ๋ฆฌ์์ Session์ ๊ด๋ฆฌํ๊ณ ์์ด ๋ถ์ฐํ๊ฒฝ์์๋ Session ๋ถ์ผ์น๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ๋๋ฌธ์ ๋ก๊ทธ์ธํ ์ฌ์ฉ์๊ฐ ์ธ์ ์ด ๋ง๋ฃ๋๊ธฐ์ ์ ๊ฐํ์ ์ผ๋ก ๋ก๊ทธ์์์ด ๋๋ ์ํฉ์ด์์ต๋๋ค.
๊ฐ์ ๊ณผ์
โ Session ๊ด๋ฆฌ ๋ฐฉ๋ฒ
๋ถ์ฐํ๊ฒฝ์ Session ๋ถ์ผ์น๋ฅผ ํด๊ฒฐ ํ๊ธฐ ์ํด์ ์ด 3๊ฐ์ง์ ์ ํ์ง๊ฐ ์์์ต๋๋ค.
1. Sticky Session
๋ก๋ ๋ฐธ๋ฐ์๊ฐ ์ฟ ํค๋ IP ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์๋ฅผ ์๋ณํ๊ณ , ํด๋น ์ฌ์ฉ์์ ์์ฒญ์ ํญ์ ๋์ผํ ์๋ฒ๋ก ๋ผ์ฐํ
์ฅ์ : ๊ตฌํ์ด ๋งค์ฐ ๊ฐ๋จํ๊ณ ๊ธฐ์กด ์ฝ๋ ๋ณ๊ฒฝ์ด ๊ฑฐ์ ์์. ์ธ์ ์ผ๊ด์ฑ์ด ์๋ฒฝํ๊ฒ ๋ณด์ฅ.
๋จ์ : ํน์ ์๋ฒ์ ๋ถํ๊ฐ ์ง์ค๋ ์ ์๊ณ , ๊ทธ ์๋ฒ์ ์ฅ์ ๊ฐ ๋ฐ์ํ๋ฉด ํด๋น ์ฌ์ฉ์๋ค์ ์ธ์ ์ด ๋ชจ๋ ์ฌ๋ผ์ง. ๋ํ ์ง์ ํ ์๋ฏธ์ ๋ถํ ๋ถ์ฐ์ด ์ด๋ฃจ์ด์ง์ง ์์ ์ ์์ต
2. Session Replication
๊ฐ ์๋ฒ๊ฐ ํด๋ฌ์คํฐ๋ฅผ ๊ตฌ์ฑํ์ฌ ์ธ์ ์ ๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ๋๊ธฐํ
์ฅ์ : ์์ ํ ๋ถํ ๋ถ์ฐ์ด ๊ฐ๋ฅํ๊ณ ์๋ฒ ์ฅ์ ์ ๊ฐํจ
๋จ์ : ์๋ฒ ๊ฐ ๋คํธ์ํฌ ํธ๋ํฝ์ด ์ฆ๊ฐํ๊ณ , ์๋ฒ ์๊ฐ ๋์ด๋ ์๋ก ๋ณต์ ์ค๋ฒํค๋๊ฐ ๊ธฐํ๊ธ์์ ์ผ๋ก ์ปค์ง
3. External Session Store
์ธ์ ์ ๋ณด๋ฅผ ์๋ฒ์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์๋ ๋ณ๋์ ์ธ๋ถ ์ ์ฅ์์ ์ ์ฅ
์ฅ์ : ํ์ฅ์ฑ์ด ๋ฐ์ด๋๊ณ ์๋ฒ๋ฅผ ์์ ๋กญ๊ฒ ์ถ๊ฐํ๊ฑฐ๋ ์ ๊ฑฐํ ์ ์์. ๋ํ ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์์์ ์ผ๋ก ์ ์ฅํ ์๋ ์์ด์ ์๋ฒ ์ฌ์์ ์์๋ ์ธ์ ์ด ์ ์ง
๋จ์ : ์ธ๋ถ ์ ์ฅ์๋ก์ ๋คํธ์ํฌ ์ง์ฐ์ด ๋ฐ์ํ๊ณ , ์ถ๊ฐ ์ธํ๋ผ ๋น์ฉ์ด ํ์
๊ฒฐ๊ณผ์ ์ผ๋ก ์ ํฌ๋ 3๋ฒ ๋ฐฉ๋ฒ์ ์ฑํํ์์ต๋๋ค. ๊ทธ ์ด์ ๋ AutoScaling ๊ธฐ๋ฐ์ ๋ถ์ฐ ํ๊ฒฝ์ผ๋ก ๋์ด ์์ด ๊ฐ๋ณ ์๋ฒ๊ฐ ์ธ์
์ ์ง์ ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์ด ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ํ ์ค์ผ์ผ ์์์ด๋ ์ธ์คํด์ค ๊ต์ฒด๊ฐ ์์๋ก ์ผ์ด๋๋ ์ํฉ์์ ์๋ฒ ๋ฉ๋ชจ๋ฆฌ์ ์ธ์
์ ๋๊ฒ ๋๋ฉด, ์ฌ์ฉ์๋ ์ธ์
์ด ์ฌ๋ผ์ ธ ์ฌ๋ก๊ทธ์ธ์ ๋ฐ๋ณตํด์ผ ํ๊ณ ์ฅ์ ๋ ์ฌ๋ฐฐํฌ ์ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๊ธฐ๋ ์ด๋ ต์ต๋๋ค.
โ ์ธ๋ถ ์ ์ฅ์ ์ ํ MySQL vs Redis
์ธ๋ถ ์ ์ฅ์๋ก๋ Redis(์ถ๊ฐํ์) vs MySQL(๊ธฐ์กด์ฌ์ฉ) 2๊ฐ์ง ์ ํ์ง๊ฐ ์์์ต๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ธ๋ถ ์ธ์ ์ ์ฅ์๋ก Redis๋ฅผ ๋ง์ด ์ฌ์ฉํ์ง๋ง, ์ ํฌ ์๋น์ค์ ํฉ๋นํ์ง ํ๋ฒ ์กฐ์ฌํด๋ดค์ต๋๋ค.
Redis ๋น์ฉ ์ฒดํฌ
์ ํฌ ์๋น์ค์์๋ Redis๋ฅผ ์ฌ์ฉํ์ ์ด ์๊ธฐ ๋๋ฌธ์, ์ฐํ
์ฝ์ ๋ค๋ฅธ ํ ์ค Redis๋ฅผ ์ฌ์ฉํ๋ ํ์ ์ธ์คํด์ค ๋น์ฉ์ ๊ฒ์ํด๋ดค์ต๋๋ค. ํน์ ์ธ์คํด์ค๋ก ๋น์ฉ์ ๊ฒ์ํ ๊ฒฝ์ฐ 14์ผ์น์ ๋น์ฉ๋ง ๊ณ์ฐ์ด ๋์๊ณ , ๋น์ฉ์ ์ฝ 6$ ์์ต๋๋ค.
๊ทธ๋ผ ์ ์ฌ์ฉ๋ฃ๋ ์ฝ 12$ ์ด๊ณ , ์ ํฌ์๊ฒ ์ ํ๋ ์ ์ฌ์ฉ๋ฃ 70$์ ์ฝ 20%๊ฐ Redis ์ฌ์ฉ ๋น์ฉ์ผ๋ก ๋๊ฐ๊ฒ ๋ฉ๋๋ค.
Session์ด ํ์ํ API ํธ์ถ ํ์ ์ฒดํฌ
Session์ด ํ์ํ admin ์ฌ์ฉ์์ api ํธ์ถ ํ์๋ฅผ ๋ณด๋ฉด, ์ ์ฒด์ ์ฝ 25% ์ ๋ ์์ต๋๋ค.
์ ์ํฉ์ ์ข ํฉํ์๋ Redis๋ฅผ ์ฌ์ฉํ๋๊ฒ์ ๋นํจ์จ์ ์ด๋ผ๊ณ ํ๋จ์ ํ์๊ณ ๊ธฐ์กด MySQL์ ์ฌ์ฉํ๋๊ฒ์ผ๋ก ํ์๋ค๊ณผ ํจ๊ป ๊ฒฐ์ ํ์ต๋๋ค.
โ Spring Session ํ์ต
Spring์์ ์ด๋ฌํ ๋ถ์ฐํ๊ฒฝ์์ Session์ ์ด๋ป๊ฒ ๊ด๋ฆฌํ๋์ง ์ฐพ์๋ณด๋ ์ค, Spring ๊ณต์ ๋ฌธ์์์ ์์ ๊ฐ์ ๋ด์ฉ์ ํ์ธํ ์ ์์์ต๋๋ค. ์ฆ, Spring Session์ ์ฌ์ฉํ๋ฉด ์ธ์
๊ด๋ฆฌ๋ฅผ DB์์ ํ ์ ์๋ค๋ ์๋ฏธ์
๋๋ค. ๊ทธ ๋์ ์๋ฆฌ๋ฅผ ํ๋ฒ ์์๋ณด๊ฒ ์ต๋๋ค.
Spring Session์ SessionRepositoryFilter๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ๊ฐ๋ก์ฑ๋๋ค. ํด๋น ํํฐ๋ HttpServletRequest, HttpServletResponse ์ ๊ตฌํ์ฒด๋ฅผ ๊ฐ๊ฐ SessionRepositoryRequestWrapper, SessionRepositoryResponseWrapper ๋ก ์์ฑํ๋๋ฐ, ์ด๋ฅผ Wrapping ์ด๋ผ๊ณ ํฉ๋๋ค. ์ด๋ ๊ฒ ๋ํ์ ํ๋ ์ด์ ๋ Tomcat์ด ๊ด๋ฆฌํ๋ ๋ฉ๋ชจ๋ฆฌ ์ธ์
์ Spring Session์ด ๊ด๋ฆฌํ๋๋ก ํ๊ธฐ ์ํจ์
๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
// 1. Request์ Response๋ฅผ Wrapping
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
response);
try {
// 2. ๋ค์ ํํฐ๋ก ์ ๋ฌ (๋ํ๋ request ์ฌ์ฉ)
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
// 3. ์์ฒญ ๋๋ ๋ ์ธ์
์ DB์ ์ปค๋ฐ
wrappedRequest.commitSession();
}
}
Spring ๊ณต์ github ์ฝ๋๋ฅผ ๋ณด๋ฉด ์์ ๊ฐ์ด HttpServletRequest ๋ฐ HttpServletResponse๊ฐ Wrapping ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1
2
3
4
5
6
7
class SessionRepositoryRequestWrapper extends HttpServletRequest {
@Override
public HttpSession getSession() {
// Tomcat ์ธ์
๋์ Repository ์ธ์
๋ฐํ
return repositorySession;
}
}
๋ํ ํด๋น ๊ตฌํ์ฒด๋ ์์ ๊ฐ์ด Session์ DB๋ฅผ ์ด์ฉํด์ ๊ด๋ฆฌํ๋๋ก ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ ํฉ๋๋ค. ๋ฐ๋ผ์ ๊ฒฐ๊ณผ์ ์ผ๋ก Wrapping๋ HttpServletRequest, HttpServletResponse๋ DispatcherServlet์ ํตํด ์ปจํธ๋กค๋ฌ๋ก ์ ๋ฌ๋๊ฒ ๋๊ณ , ์ต์ข
์ ์ผ๋ก ๋คํ์ฑ์ ํตํด Session์ DB๋ฅผ ํตํด ์กฐํ, ์ ์ฅํ ์ ์๊ฒ ๋ฉ๋๋ค.
โ Spring Session ์ ์ฉ
Spring Session์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ ๋ง ๊ฐ๋จํฉ๋๋ค. session ํ ์ด๋ธ ์์ฑ๊ณผ ์ด๋ ธํ ์ด์ ์ถ๊ฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
@EnableJdbcHttpSession ์ด๋
ธํ
์ด์
์ผ๋ก Spring Session ํ์ฑํ
1
2
3
4
5
6
@Configuration
@EnableJdbcHttpSession(
maxInactiveIntervalInSeconds = 14 * 24 * 60 * 60 // ์ธ์
๋ง๋ฃ 2์ฃผ
)
public class SessionConfig {
}
@EnableJdbcHttpSession๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด Spring์ SessionRepositoryFilter ๋น์ ์์ฑํ๊ณ Servlet Container์ ๋ฑ๋กํ๊ฒ ๋ฉ๋๋ค.
Session ํ ์ด๋ธ ์์ฑ
1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE IF NOT EXISTS SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL PRIMARY KEY,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
UNIQUE INDEX spring_session_ix1 (SESSION_ID),
INDEX spring_session_ix2 (EXPIRY_TIME),
INDEX spring_session_ix3 (PRINCIPAL_NAME)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1
2
3
4
5
6
7
CREATE TABLE IF NOT EXISTS SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Session ๊ด๋ฆฌ์ ํ์ํ ํ ์ด๋ธ์ 2๊ฐ์ง ์ ๋๋ค. ๊ฐ ํ ์ด๋ธ์ ์ญํ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- SPRING_SESSION : ์ธ์ ์ ์์ฑ์๊ฐ, ๋ง๋ฃ์๊ฐ ๋ฑ ๊ธฐ๋ณธ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๋ฉ์ธ ํ ์ด๋ธ
- SPRING_SESSION_ATTRIBUTES : ์ธ์ ์ ์ ์ฅ๋ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ํ ์ด๋ธ
1
2
3
4
5
INSERT INTO SPRING_SESSION_ATTRIBUTES VALUES (
'abc-123-def', -- SESSION_PRIMARY_ID
'user', -- ATTRIBUTE_NAME
<์ง๋ ฌํ๋ User ๊ฐ์ฒด> -- ATTRIBUTE_BYTES
);
์๋ฅผ ๋ค์ด session.setAttribute("user", user)๋ก ์ ์ ์ธ์
์ ๋ณด๋ฅผ ์ ์ฅํ๋ค๋ฉด, ์์ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
์ฑ๊ณผ
โ ์ถ๊ฐ ์ธํ๋ผ ๋น์ฉ 20% ์ ๊ฐ
โ ๋ถ์ฐํ๊ฒฝ Session ๋ถ์ผ์น ๋ฌธ์ ํด๊ฒฐ
๋ง๋ฌด๋ฆฌํ๋ฉฐ
ํ์ ๋ชจ๋ ๋ง์กฑํ๋ ๊ฒฐ๊ณผ๋ผ ๋ฟ๋ฏํ๊ณ , ๊ธฐ์ ์ ์ ํํจ์ ์์ด ๋น์ฉ๊ณผ ๋ณต์ก๋๋ฅผ ๊ณ ๋ คํ๋๊ฒ์ด ์ผ๋ง๋ ์ค์ํ์ง๋ฅผ ๊นจ๋ฌ์์ต๋๋ค. ๋จ์ํ ์ข๋ค ๋ผ๋ ๊ด์ ์์ Redis๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ๊ด๋ฆฌ ๋น์ฉ๊ณผ ๋ณต์ก๋ ๋ชจ๋ ์ฌ๋ผ๊ฐ์ ๊ฒ์ ๋๋ค.
๋ํ ์ด๋ฒ ๊ธฐํ์ Spring Session์ ์๋ฆฌ์ ๋ํด์๋ ๊น๊ฒ ํ์ตํ ์ ์์์ต๋๋ค. Wrapping ํจํด์ ํตํด Tomcat์ ์ธ์
๊ด๋ฆฌ๋ฅผ ํฌ๋ช
ํ๊ฒ ๋์ฒดํ๋ ์ค๊ณ๊ฐ ํนํ ์ธ์์ ์ด์๊ณ , ์ด๋
ธํ
์ด์
ํ๋๋ก ์ธ์
์ ์ฅ์๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ค๋ ์ ์์ ์ถ์ํ์ ๋คํ์ฑ์ ์ค์์ฑ์ ํ๋ฒ ๋ ๊นจ๋ฌ์ ์ ์์์ต๋๋ค.



