๊ฐ์
์ต๊ทผ ๊ฐ๋ฐ ์ค์ด๋ ์๋น์ค์ ๊ฒ์๊ธ ์กฐํ API์์ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ํ ์ฑ๋ฅ ํ ์คํธ๋ฅผ ์งํํ๋ ๊ณผ์ ์์, ๋ฐ์ดํฐ ์ถ๊ฐ ์ด์ ์๋ ๋ํ๋์ง ์์๋ ๋ฌธ์ ์ ๋ค์ ๋ฐ๊ฒฌํ๊ฒ ๋์์ต๋๋ค. ๊ธฐ์กด์๋ ์ํํ๊ฒ ์๋ํ๋ API๊ฐ ๋ฐ์ดํฐ๊ฐ ๊ธ๊ฒฉํ ์ฆ๊ฐํ์ ์๋ต ์๋ ์ง์ฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ ๊ณผ๋ถํ, ์ผ๋ถ ์์ฒญ์์์ ์์ธ ๋ฐ์ ๋ฑ ๋ค์ํ ์ฑ๋ฅ ์ ํ ํ์์ ๋ณด์์ต๋๋ค.
์ด๋ฌํ ์์์น ๋ชปํ ์ฑ๋ฅ ์ ํ ํ์์ ๋จ์ํ ๋ฐ์ดํฐ ์์ด ๋์ด๋ฌ๊ธฐ ๋๋ฌธ์ด ์๋๋ผ, ์ฆ๊ฐ๋ ๋ฐ์ดํฐ ๋ณผ๋ฅจ์ ๊ธฐ์กด ์์คํ ์ด ์ด๋ป๊ฒ ๋ฐ์ํ๋์ง์ ๋ํ ๋ฉด๋ฐํ ๋ถ์์ด ํ์ํ๋ค๋ ์ ์ ์์ฌํ์ต๋๋ค. ์ฆ, ๊ธฐ์กด์๋ ๋๋ฌ๋์ง ์์๋ ์์คํ ์ ํ๊ณ๋ ํน์ ๋ก์ง์ ๋นํจ์จ์ฑ์ด ๋์ฉ๋ ๋ฐ์ดํฐ ํ๊ฒฝ์์ ๋ฐํ๋ ๊ฒ์ผ๋ก ํ๋จํ์ต๋๋ค.
๋ณธ ๊ธ์์๋ ๋ค์ํ ๊ณ์ธต์ ์งํ๋ค์ ์ข ํฉ์ ์ผ๋ก ๋ถ์ํด ์ฑ๋ฅ ์ ํ์ ๊ทผ๋ณธ ์์ธ๊ณผ ๋ณ๋ชฉ ์ง์ ์ ๊ท๋ช ํ๊ณ , ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ด๋ค ์กฐ์น๋ฅผ ์ทจํ๋์ง ๊ทธ ๊ณผ์ ์ ์์ธํ ์๊ฐํ๊ณ ์ ํฉ๋๋ค.
ํ ์คํธ ๋ฐ์ดํฐ
์ด๋ฒ ์ฑ๋ฅ ํ ์คํธ์ ์ฌ์ฉ๋ ๋ฐ์ดํฐ ๊ท๋ชจ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Table | Size |
user | 1,000 |
post_like | 50,000 |
post | 1,000,000 |
content | 10,000,000 |
ํ ์คํธ ์๋๋ฆฌ์ค
ํ ์คํธ๋ฅผ ์ํด ๋ค์๊ณผ ๊ฐ์ k6 ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์ฌ ๊ฒ์๊ธ ์กฐํ API์ ๋ํ ๋ถํ ์๋๋ฆฌ์ค๋ฅผ ์ ์ํ์ต๋๋ค.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
export let options = {
stages: [
{ duration: '10s', target: 10 },
{ duration: '10s', target: 50 },
{ duration: '10s', target: 100 },
{ duration: '10s', target: 150 },
{ duration: '10s', target: 200 },
{ duration: '10s', target: 250 },
],
};
export let errorRate = new Rate("errors");
export default function() {
const page = Math.floor(Math.random() * 10) + 1
const url = `http://localhost:8080/v1/posts?page=${page}&size=10`;
let res = http.get(url);
let success = check(res, { 'get-posts': (r) => r.status === 200 });
if (!success) {
errorRate.add(1);
}
sleep(1);
}
์ ์คํฌ๋ฆฝํธ๋ ์ด 60์ด ๋์ ๊ฐ์ ์ฌ์ฉ์๋ฅผ 10๋ช ์์ 250๋ช ๊น์ง ์ ์ง์ ์ผ๋ก ์ฆ๊ฐ์ํค๋ฉฐ ๊ฒ์๊ธ ์กฐํ API๋ฅผ ํธ์ถํ๋ ์๋๋ฆฌ์ค๋ฅผ ๋ด๊ณ ์์ต๋๋ค.
๋ฌธ์ ์ํฉ
๋ฌธ์ ๋ MySQL์ ๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ํ K6 ๋ถํ ํ ์คํธ๋ฅผ ์คํํ์์ ๋ ๋ฐ์ํ์์ต๋๋ค.
์ฃผ์ด์ง ์งํ๋ฅผ ๋ฐํ์ผ๋ก ์ฑ๋ฅ ํํฉ์ ๋ถ์ํ ๊ฒฐ๊ณผ, ์ฌ๊ฐํ ์์ค์ ์๋ต ์๊ฐ ์ง์ฐ๊ณผ ๋๋ถ์ด ์ค๋ฅ์จ์ด ๊ธ์ฆํ๊ณ ์์์ด ํ์ธ๋์์ต๋๋ค.
๊ตฌ์ฒด์ ์ผ๋ก ์ดํด๋ณด๋ฉด, HTTP ์์ฒญ์ ๋ํ ์๋ต ์๊ฐ(http_req_duration)์ด ๋น์ ์์ ์ผ๋ก ๊ธธ์ด์ง๊ณ ์์ต๋๋ค. ํ๊ท (mean) ์๋ต ์๊ฐ์ด 27.76s์ ๋ฌํ๋ฉฐ, ์ ์ฒด ์์ฒญ์ 90%(p90)๊ฐ 35.59s, 95%(p95)๊ฐ 35.61s ์ด๋ด์ ์๋ต์ ๋ฐ๋ ๊ฒ์ผ๋ก ๋ํ๋ฌ์ต๋๋ค. ๊ฐ์ฅ ๋นจ๋๋ ์๋ต ์๊ฐ ๋ํ 1.68์ด๋ก, API์ ์ผ๋ฐ์ ์ธ ์ฑ๋ฅ ๊ธฐ๋์น์ ๋น์ถ์ด ๋ณผ ๋ ๊ฒฐ์ฝ ๋น ๋ฅธ ํธ์ด ์๋์์ต๋๋ค. ์ด๋ฌํ ์์น๋ ๋๋ถ๋ถ์ ์ฌ์ฉ์ ์์ฒญ์ด ์ ์์ ์ธ ์๋ต์ ๋ฐ๊ธฐ๊น์ง ์ ์ด์์ ์์ญ ์ด๊น์ง ์์๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
๋์ฑ์ด, ์ด๋ฌํ ์๋ต ์ง์ฐ ํ์๊ณผ ํจ๊ป ํน์ ์์ ์์ Error per Second ์งํ๊ฐ ๊ธ์ฆํ๋ ์์์ด ๋๋ ทํ๊ฒ ๊ด์ธก๋์์ต๋๋ค. ์ด๋ ๋จ์ํ ๋๋ ค์ง๋ ๊ฒ์ ๋์ด, ์์ฒญ์ ์ ์์ ์ผ๋ก ์ฒ๋ฆฌํ์ง ๋ชปํ๊ณ ์๊ฑฐ๋ ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๊ฐ๋ฅ์ฑ์ ๊ฐ๋ ฅํ๊ฒ ์์ฌํฉ๋๋ค. ์ฆ, ์ผ๋ถ ์์ฒญ์ ์์ ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋์ง ๋ชปํ๊ณ ์ค๋ฅ๋ฅผ ๋ฐํํ๊ณ ์๋ค๋ ์๋ฏธ์ ๋๋ค.
๊ทผ๋ณธ ์์ธ ์ฐพ๊ธฐ
๋ฌธ์ ์ ๊ทผ๋ณธ์ ์ธ ์์ธ์ ํ์ ํ๊ธฐ ์ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฆฌ์์ค ์ฌ์ฉ ํํฉ์ ๋ถ์ํ ๊ฒฐ๊ณผ, CPU ์ฌ์ฉ๋ฅ ์ด ์ผ์์ ์ผ๋ก 90% ์ด์๊น์ง ๊ธ์ฆํ ์ํฉ์ด ํ์ธ๋์์ต๋๋ค. ์ด๋ฌํ CPU ์ฌ์ฉ๋ฅ ์ ๊ธ์ฆ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ์ฒ๋ฆฌ ๋ฅ๋ ฅ์ ์ฌ๊ฐํ ๋ณ๋ชฉ ํ์์ ์ด๋ํ๊ณ , ์ด๋ ์ ๋ฐ์ ์ธ ์๋ต ์๊ฐ ์ง์ฐ์ผ๋ก ์ด์ด์ก์ต๋๋ค.
์ด๋ฌํ ์ง์ฐ์ Spring ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉํ๋ HikariCP ์ปค๋ฅ์ ํ์๋ ์ํฅ์ ๋ฏธ์ณค์ต๋๋ค. ์ปค๋ฅ์ ์ด ํ๋ณด๋ ํ์๋ ์ฟผ๋ฆฌ ์ํ์ด ์ง์ฐ๋๋ฉด์ ์ปค๋ฅ์ ๋ฐํ์ด ๋ฆ์ด์ก๊ณ , ๊ทธ ๊ฒฐ๊ณผ ์ปค๋ฅ์ ํ ๋ด์์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ปค๋ฅ์ ์ด ๋ถ์กฑํด์ง๋ ์ํฉ์ด ๋ฐ์ํ์ต๋๋ค. ์ปค๋ฅ์ ํ์ด ๋ชจ๋ ์ ์ ๋๋ฉด์ ์ดํ ์์ฒญ์ ์ปค๋ฅ์ ์ ํ ๋น๋ฐ์ง ๋ชปํ ์ฑ Pending ์ํ๋ก ๋๊ธฐํ๊ฒ ๋์๊ณ , ๊ฒฐ๊ตญ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ์ง ๋ชปํ๊ณ ์ค๋ฅ๋ฅผ ๋ฐํํ๋ ์ํฉ์ผ๋ก ์ด์ด์ก์ต๋๋ค.
์ด๋ฌํ ํ์์ ์์ธ์ ๋ณด๋ค ๊ตฌ์ฒด์ ์ผ๋ก ํ์
ํ๊ธฐ ์ํด ๊ฒ์๊ธ ์กฐํ API์ ์ฌ์ฉ๋๋ ์ฟผ๋ฆฌ๋ฅผ EXPLAIN ANALYZE
๋ฅผ ํตํด ์คํ ๊ณํ์ ๋ถ์ํ ๊ฒฐ๊ณผ, ํด๋น ์ฟผ๋ฆฌ๋ค์ด ๋น์ ์์ ์ผ๋ก ๊ธด ์คํ ์๊ฐ์ ๊ธฐ๋กํ๊ณ ์์์์ ํ์ธํ ์ ์์์ต๋๋ค. ํด๋น ์ฟผ๋ฆฌ๋ค์ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง๊ฒ ๋๋ฉด์ ๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๊ฑฐ๋ ์ ์ ํ ์ธ๋ฑ์ค๊ฐ ์ ์ฉ๋์ง ์์ ์ํ๋ก ์คํ๋๊ณ ์์์ต๋๋ค. ์ด๋ก ์ธํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ CPU ์์์ด ๊ธ๊ฒฉํ ์๋ชจ๋์๊ณ , ์์ ์ธ๊ธํ ์ผ๋ จ์ ๋ฌธ์ ๋ฅผ ์ ๋ฐํ ์ง์ ์ ์ธ ์์ธ์ผ๋ก ํ๋จํ๊ฒ ๋์์ต๋๋ค.
์ฟผ๋ฆฌ ๋ถ์
๊ฒ์๊ธ ์ ์ฒด ์กฐํ API๋ ๊ฒ์๊ธ๊ณผ ํด๋น ๊ฒ์๊ธ์ ํฌํจ๋ ์ฝํ ์ธ ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ์กฐํ ์ ๊ฐ ๊ฒ์๊ธ์ ์ ๋ณด์ ํจ๊ป ์ฝํ ์ธ ์ค ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์ ์ฒซ ๋ฒ์งธ ํ ์คํธ๋ฅผ ํจ๊ป ๋ฐํํด์ผ ํ๋ค๋ ์๊ตฌ์ฌํญ์ด ์์์ต๋๋ค. ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด, ๋จผ์ ๊ฒ์๊ธ๋ค์ ์กฐํํ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ก ์ป์ ๊ฒ์๊ธ ID ๋ชฉ๋ก์ ๊ธฐ๋ฐ์ผ๋ก ํด๋นํ๋ ์ฝํ ์ธ ๋ค์ ์กฐํํ๋ ์ฟผ๋ฆฌ๋ฅผ Querydsl๋ก ์์ฑํ์ฌ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค.
์ฌ์ฉ๋ ์ฟผ๋ฆฌ
์ฃผ์ด์ง ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ ์ ์๋์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค.
@Service
@Transactional(readOnly=true)
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
private final ContentRepository contentRepository;
private final UserRepository userRepository;
public SliceResponse<PostFindAllResponse> findAllPost(OffsetLimit offsetLimit) {
// 1. ๊ฒ์๊ธ ํ์ด์ง ์กฐํ
List<PostQueryEntity> posts = postRepository.findAllPost(offsetLimit);
// 2. ๊ฒ์๊ธ ID๋ค์ ํด๋นํ๋ ์ฝํ
์ธ ์กฐํ
List<ContentQueryEntity> contents = contentRepository.findAllByPostIds(posts.getIds());
// 3. PostFindAllRespose์์ ๋ฐ์ดํฐ ๊ฒฐํฉ
return SliceResponse.of(PostFindAllResponse.toList(posts, contents), offsetLimit);
}
}
postRepository.findAllPost(offsetLimit)
๋ฅผ ํตํด ํ์ด์ง ๋ฐ ์ฌ์ด์ฆ์ ํด๋นํ๋ ๊ฒ์๊ธ ์ ๋ณด๋ฅผ ๋จผ์ ์กฐํํฉ๋๋ค. ์ด์ด์ contentRepository.findAllByPostIds(postIds)
๋ฅผ ์ฌ์ฉํ์ฌ ์กฐํ๋ ๊ฒ์๊ธ ID๋ค์ ํด๋นํ๋ ๋ชจ๋ ์ฝํ
์ธ ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ต์ข
์ ์ผ๋ก PostFindAllResponse
์์ ๊ฒ์๊ธ ๋ฐ์ดํฐ์ ์ฝํ
์ธ ๋ฐ์ดํฐ๋ฅผ ๊ฒฐํฉํ๋๋ฐ, ์ด๋ ๊ฒ์๊ธ๊ณผ postId
๊ฐ ์ผ์นํ๋ ์ฝํ
์ธ ์ค ContentType
์ด TEXT
์ธ ๊ฒ์ค contentId
๊ฐ ๊ฐ์ฅ ์์ ๊ฒ ํ๋์ ContentType
์ด IMAGE
์ธ ๊ฒ ์ค contentId
๊ฐ ๊ฐ์ฅ ์์ ๊ฒ ํ๋๋ฅผ ์ ํํ์ฌ ๊ฒฐํฉํ ํ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํฉ๋๋ค.
์์ ์ค๋ช ํ ์ฝํ ์ธ ์กฐํ ์กฐ๊ฑด์ ๋ง์กฑ์ํค๊ธฐ ์ํด Querydsl์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์๊ณ , ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ค์ ๋ก ์คํ๋๋ SQL ์ฟผ๋ฆฌ๋ ์๋์ ๊ฐ์ต๋๋ค.
1. ๊ฒ์๊ธ ํ์ด์ง ์กฐํ
SELECT
p.post_id as postId,
p.post_title as postTitle,
u.user_name as username,
u.user_profile_image as userProfileImage,
p.registered_date_time as postRegisteredDateTime,
count(pl.id) as likeCount
FROM
post p
INNER JOIN
users u on u.user_id = p.user_id
LEFT JOIN
post_like pl on pl.post_id = p.post_id
GROUP BY
p.post_id, p.post_title, u.user_name, u.user_profile_image, p.registered_date_time
ORDER BY
p.post_id desc
LIMIT 10
OFFSET 0;
2. ๊ฒ์๊ธ ID์ ํด๋นํ๋ ์ฝํ ์ธ ์กฐํ
SELECT
c.post_id as postId,
c.content_id as contentId,
c.content_type as contentType,
c.content_value as contentValue
FROM
content c
WHERE
c.post_id in (post_ids);
์ด์ ๋ถํฐ ์์ ์์ฑํ ์ฟผ๋ฆฌ๋ค์ด ๋ฐ์ดํฐ ์์ด ๋ง์์ง์๋ก ์ ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ์ผํค๋์ง, ๊ทธ ์์ธ์ ๊ตฌ์ฒด์ ์ผ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๊ฒ์๊ธ ํ์ด์ง ์กฐํ ์ฟผ๋ฆฌ ๋ถ์
ํด๋น ์ฟผ๋ฆฌ์ ๋ํด EXPLAIN ANALYZE
๋ฅผ ์ํํ ๊ฒฐ๊ณผ, ์๋์ ๊ฐ์ ์คํ ๊ณํ์ด ์ถ๋ ฅ๋์์ต๋๋ค.
์ ์คํ ๊ณํ์ ๋ถ์ํด ๋ณด๋ฉด, ์ฌ๋ฌ ์ฑ๋ฅ ์ ํ ์์ธ์ด ๋ณตํฉ์ ์ผ๋ก ์์ฉํ๊ณ ์์์ ํ์ธํ ์ ์์ต๋๋ค.
1. ๋๋์ ๋ฐ์ดํฐ ์กฐ์ธ
์ฐ์ , ์ธ๋ฑ์ค ์ค์บ์ ์ฌ์ฉํ๋๋ผ๋ user์ post ํ ์ด๋ธ ๊ฐ ์กฐ์ธ ๊ณผ์ ์์ ์๋นํ ์๊ฐ์ด ์์๋๊ณ ์์ต๋๋ค. ์ด๋ ์กฐ์ธ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ง์ ์์ ๋ฐ์ดํฐ๋ฅผ ํ์ํด์ผ ํ๋ฉฐ, ํจ์จ์ ์ธ ์ธ๋ฑ์ค ํ์ฉ์ด ์ ํ์ ์ธ ๊ตฌ์กฐ์ผ ๊ฒฝ์ฐ ๋์ฑ ์ฌ๊ฐํ ๋ณ๋ชฉ์ผ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
2. ์ข์์ ์ง๊ณ ์ ๋ฐ์ํ๋ ๊ทธ๋ฃนํ ๋น์ฉ
๋ํ, ๊ฒ์๊ธ์ ๋ํ ์ข์์ ์๋ฅผ ์ง๊ณํ๊ธฐ ์ํ ์ฐ์ฐ์์ ๋๋์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ ์ด๋ธ์ ๋ก๋ํ ํ ๊ทธ๋ฃนํํ๋ ๊ณผ์ ์ด ๋ฐ์ํ๋๋ฐ, ์ด ๋จ๊ณ์์ ๋งค์ฐ ๋์ ๋น์ฉ์ด ๋ค๊ณ ์์ต๋๋ค. ํนํ ์ข์์ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง์๋ก ๊ทธ๋ฃนํ์ ํ์ํ ์ฐ์ฐ๋๋ ๊ธฐํ๊ธ์์ ์ผ๋ก ์ฆ๊ฐํ๊ฒ ๋๋ฉฐ, ์ด๋ ์ ์ฒด ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์น๋ช ์ ์ธ ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
3. OFFSET/LIMIT์ ๋นํจ์จ์ ์ธ ํ์ด์ง ์ฒ๋ฆฌ
๋ง์ง๋ง์ผ๋ก, ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ํด ์ฌ์ฉ๋ OFFSET๊ณผ LIMIT์ ์ด ์คํ ๊ณํ์ ๊ฐ์ฅ ๋ง์ง๋ง ๋จ๊ณ์์ ์ ์ฉ๋๊ณ ์๊ธฐ ๋๋ฌธ์, ์ค์ ๋ก ํ์ํ ์์์ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ธฐ ์ํด์๋ ์ ์ฒด ๊ฒฐ๊ณผ ์ ์ ๊ฑฐ์ ๋ชจ๋ ์ฒ๋ฆฌํด์ผ ํ๋ ๋นํจ์จ์ด ๋ฐ์ํ๊ณ ์์ต๋๋ค. ์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์์ ํนํ ๋ฐ์ดํฐ ์์ด ๋ง์ ์ํฉ์์ ๋ถํ์ํ ๋ฐ์ดํฐ ์ฝ๊ธฐ์ ์ ๋ ฌ์ ์ ๋ฐํ์ฌ, ์ ์ฒด ์ฟผ๋ฆฌ์ ์๋ต ์๊ฐ์ ํฌ๊ฒ ์ง์ฐ์ํค๋ ์์ธ์ด ๋ฉ๋๋ค.
๊ฒ์๊ธ ID์ ํด๋นํ๋ ์ฝํ ์ธ ์กฐํ ์ฟผ๋ฆฌ ๋ถ์
ํด๋น ์ฟผ๋ฆฌ์ ๋ํด EXPLAIN ANALYZE
๋ฅผ ์ํํ ๊ฒฐ๊ณผ, ์๋์ ๊ฐ์ ์คํ ๊ณํ์ด ์ถ๋ ฅ๋์์ต๋๋ค.
ํด๋น ์คํ ๊ณํ์ ๋ณด๋ฉด ํฌ๊ฒ ์ง์ฐ๋ ๋งํ ํน๋ณํ ๋ถ๋ถ์ ๋์ ๋์ง ์์ต๋๋ค. ์คํ ๊ณํ ์์ฒด๋ ์ธ๋ฑ์ค๋ฅผ ์ ํ์ฉํ๊ณ ์์ด ํฐ ๋ฌธ์ ๊ฐ ์์ด๋ณด์ด์ง๋ง, ๊ฐ ๋จ๊ณ๋ณ๋ก ์ฒ๋ฆฌ๋๋ ์ค์ ๋ฐ์ดํฐ ์(Actual Rows)์ด ์๋นํ ๋ง๋ค๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
์ด์ฒ๋ผ, ์ธ๋ฑ์ค ์ค์บ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ๊ฐ์ ธ์จ๋ค๊ณ ํด๋, 20,000๊ฐ๋ผ๋ ์๋นํ ์์ ์ฝ์ด์ผ ํ๋ ์ ์ด ๋ฌธ์ ์ ๋๋ค. ์ธ๋ฑ์ค๋ฅผ ์ ํ์ฉํ๊ณ ์์์๋ ๋ถ๊ตฌํ๊ณ , ๊ฒฐ๊ตญ ์ฒ๋ฆฌํด์ผ ํ ๋ฐ์ดํฐ์ ์ ๋์ ์ธ ์์ด ๋ง๋ค๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค. ์ด๋ ๊ฒ ๋ถ์ด๋ ๋ฐ์ดํฐ๋ ์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ ๋ฌ๋ ๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฆ๊ฐ๋ก ์ด์ด์ ธ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ๋ถ์ ์ ์ธ ์ํฅ ๋ํ ๋ฏธ์น ์ ์์ต๋๋ค.
์ฟผ๋ฆฌ ๊ฐ์
๊ฒ์๊ธ ์กฐํ ์ฟผ๋ฆฌ ๊ฐ์
SELECT
p.post_id as postId,
p.post_title as postTitle,
u.user_name as username,
u.user_profile_image as userProfileImage,
p.registered_date_time as postRegisteredDateTime,
count(pl.id) as likeCount
FROM
post p
INNER JOIN
users u on u.user_id = p.user_id
INNER JOIN (
SELECT post_id
FROM post
ORDER BY post_id desc
LIMIT 10
OFFSET 0
) as latest_post on latest_post.post_id = p.post_id
LEFT JOIN
post_like pl on pl.post_id = p.post_id
GROUP BY
p.post_id, p.post_title, u.user_name, u.user_profile_image, p.registered_date_time;
๊ธฐ์กด ์ฟผ๋ฆฌ์์๋ ์ต์ ๊ฒ์๋ฌผ์ ๊ฐ์ ธ์ค๊ธฐ ์ํด ๋ชจ๋ post
, user
, post_like
๋ฐ์ดํฐ๋ฅผ ์กฐ์ธํ ํ ์ ๋ ฌํ๊ณ ์ง๊ณํ๋ ๋ฐฉ์์ด์์ต๋๋ค. ์ด๋ก ์ธํด ์ฟผ๋ฆฌ๊ฐ ๋ง์์ง์๋ก ์ ์ฒด ํ
์ด๋ธ์ ๋ํ ์ฐ์ฐ์ด ๋ฐ์ํ์ฌ ์ฑ๋ฅ์ด ์ ํ๋ ์ํฉ์ด์์ต๋๋ค. ์ด๋ฅผ ๊ฐ์ ํ๊ณ ์ ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ๋ ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์, post_id๋ฅผ ๊ธฐ์ค์ผ๋ก limit์ ๋ง๊ฒ ๋จผ์ ์ ๋ณํ๋๋ก ์ฟผ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ณ๊ฒฝํ์ต๋๋ค.
ํนํ, ์ด ์๋ธ์ฟผ๋ฆฌ๋ post_id
์ปฌ๋ผ๋ง ์ ํํ๊ณ post_id
๋ก ์ ๋ ฌํ๊ธฐ ๋๋ฌธ์, post_id
์ ์ด๋ฏธ ์์ฑ๋์ด ์๋ ํด๋ฌ์คํฐ๋ง ์ธ๋ฑ์ค๊ฐ ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ์ญํ ์ ํ๊ฒ ๋ฉ๋๋ค. ์ฆ, ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ค์ ํ
์ด๋ธ ๋ฐ์ดํฐ์ ์ ๊ทผํ ํ์ ์์ด ์ธ๋ฑ์ค๋ง์ผ๋ก ํ์ํ post_id
๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ์๋ผ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ฐพ์์ง ์ต์ํ์ post_id
๋ฐ์ดํฐ๋ง ๊ฐ์ง๊ณ ๋๋จธ์ง ํ
์ด๋ธ๋ค๊ณผ ์กฐ์ธ์ ์ํํจ์ผ๋ก์จ, ๋ถํ์ํ ๋๊ท๋ชจ ์กฐ์ธ ์ฐ์ฐ์ ํผํ ์ ์์ต๋๋ค.
๊ฒ์๊ธ ID์ ํด๋นํ๋ ์ฝํ ์ธ ์กฐํ ์ฟผ๋ฆฌ ๊ฐ์
create index idx_content_post_id_type_id on content(post_id, content_type, content_id);
SELECT
c.content_id as contentId,
c.post_id as postId,
c.content_type as contentType,
c.content_value as contentValue
FROM
content c
INNER JOIN (
SELECT
MIN(c_min.content_id) as min_content_id
FROM
content c_min
WHERE
c_min.post_id in (post_ids)
GROUP BY
c_min.post_id, c_min.content_type
) as min_ids on content.id = min_ids.min_content_id;
๊ธฐ์กด ์ฟผ๋ฆฌ๋ ํน์ post_ids
์ ํด๋นํ๋ ๋ชจ๋ ์ฝํ
์ธ ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ด์์ต๋๋ค. ์ด ๋ฐฉ์์ post_id
๋น ์ฌ๋ฌ ์ฝํ
์ธ ๊ฐ ์์ ๋, ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ก๋ฐ์ ์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ์์ ๋ค์ ํํฐ๋งํ๊ธฐ์ ๋ถํ์ํ๊ฒ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ ๋นํจ์จ์ด ์์์ต๋๋ค.
ํ์ง๋ง ์ค์ ๋ก ํ์ํ ๋ฐ์ดํฐ๋ post_id
์ content_type
์ผ๋ก ๊ทธ๋ฃนํ๋ ๋ฐ์ดํฐ ์ค ์ต์ content_id
๋ฅผ ๊ฐ์ง ๋ ์ฝ๋๋ค์ด์๊ธฐ ๋๋ฌธ์, ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ํ์๊ฐ ์ ํ ์์์ต๋๋ค. ์ด ์ฟผ๋ฆฌ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด content
ํ
์ด๋ธ์ (post_id, content_type, content_id)
๋ณตํฉ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๊ณ , ์๋ธ์ฟผ๋ฆฌ์์ ์ด ์ธ๋ฑ์ค๋ฅผ ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ก ํ์ฉํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ง์ ํจ์จ์ ์ผ๋ก ์กฐํํ ์ ์๋๋ก ์์ ํ์ต๋๋ค.
์คํ ๊ณํ์ ๋ณด๋ฉด content
ํ
์ด๋ธ์ ๋ํ ๋ถํ์ํ ์ ์ฒด ์ค์บ์์ด, ์ธ๋ฑ์ค๋ง์ผ๋ก post_id
๋ฐ content_type
์ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํํฐ๋ง ํ๊ณ ํ์ํ content_id
๊น์ง ๋น ๋ฅด๊ฒ ์กฐํํ๊ณ MIN(content_id)
์ง๊ณ๊ฐ ์ด๋ฃจ์ด์ง๋๋ค. ์ด๋ ๊ฒ ์ธ๋ฑ์ค๋ง์ผ๋ก ์ด๋ฏธ ์ต์ํ๋ ๋ฐ์ดํฐ์
์ ์ถ์ถํ์ฌ ์กฐ์ธํ๊ธฐ ๋๋ฌธ์, ๋ถํ์ํ๊ฒ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ๋ก ์ ์กํ๋ ์ค๋ฒํค๋๋ฅผ ์์ ๊ณ ์ฟผ๋ฆฌ ์๋๋ ๊ฐ์ ํ ์ ์์์ต๋๋ค.
๊ฐ์ ๊ฒฐ๊ณผ
์์ ๊ฐ์ด ์ฟผ๋ฆฌ ๊ฐ์ ์ ์๋ฃํ ํ, K6 ๋ถํ ํ ์คํธ๋ฅผ ๋ค์ ์ํํ์ฌ ์์คํ ์ ์ฑ๋ฅ ๋ณํ๋ฅผ ์ธก์ ํ์์ต๋๋ค.
๋ถํํ ์คํธ ์งํ
๊ฐ์ ์
๊ฐ์ ํ
๊ฐ์ ์ ์๋ HTTP ์์ฒญ ์ฒ๋ฆฌ ์๊ฐ์ด ์ฌ๊ฐํ ๋ฌธ์ ์์ต๋๋ค. ํ๊ท ์๋ต ์๊ฐ์ด 27.76s, p90 ์งํ์์ 34.59s๋ก ์ ๋ฐ์ ์ผ๋ก ๋์ ์๋ต ์๊ฐ์ด ์ง์๋๊ณ ์์์ต๋๋ค. ๋ํ ์๋ต ์๊ฐ๋ฌธ์ ์ธ์๋ ์๋ฌ๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ฐ์ํ๋ ํจํด์ ๋ณด์ด๋ ์ํฉ์ด์์ต๋๋ค.
๊ฐ์ ํ์๋ HTTP ์์ฒญ ์ฒ๋ฆฌ ์๊ฐ์ด ํ๊ธฐ์ ์ผ๋ก ๋จ์ถ๋์์ต๋๋ค. ํ๊ท ์๋ต ์๊ฐ์ด 5.84ms, p90 ์งํ์์ 7.43ms๋ก ๋ํญ ๊ฐ์ํ์ต๋๋ค. ๋ํ ๊ณผ๊ฑฐ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ฐ์ํ๋ ์๋ฌ ๋ฐ์๋ฅ ์ด ์์ ํ ์ฌ๋ผ์ก์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ด๋ จ ์งํ
๊ฐ์ ์ ์๋ ๋์ผํ ๊ธฐ๋ฅ์ ํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฎ์ QPS์์๋ CPU๋ฅผ ๊ณผ๋ํ๊ฒ ์๋ชจํ๋ฉฐ ์์คํ ๋ณ๋ชฉ์ ์ ๋ฐํ์ง๋ง, ์ฟผ๋ฆฌ ๊ฐ์ ํ์๋ QPS๋ฅผ ํ๊ธฐ์ ์ผ๋ก ๋์ด๋ฉด์๋ CPU ์ฌ์ฉ๋์ ์คํ๋ ค ํ์ ํ ๋ฎ์ถ๋ ๋ฐ ์ฑ๊ณตํ์ต๋๋ค.
๋ํ, ์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ์์์ HikariCP Connection Pool
๋ํ ๊ฐ์ ์ ํตํด ํฐ ๋ณํ๋ฅผ ๋ณด์์ต๋๋ค. ์ด์ ์๋ 222ํ์ ํ์์์๊ณผ ์ต๋ 189๊ฐ์ ๋๊ธฐ(Pending) ์ปค๋ฅ์
์ด ๋ฐ์ํ์์ผ๋, ์ด์ ๋ ํ์์์์ด ์ ํ ์๊ณ ๋๊ธฐ ์ปค๋ฅ์
๋ ์๋ ์์ ์ ์ธ ์ปค๋ฅ์
ํ ์ํ๋ฅผ ์ ์งํ๋๋ก ๊ฐ์ ๋์์ต๋๋ค.
์ต๋ ๋ถํ ์ฑ๋ฅ
๊ฐ์ ๋ ์์คํ ์ ์ต๋ ์ฑ๋ฅ์ ํ์ธํ๊ธฐ ์ํด 6,000 VUs ํ๊ฒฝ์์ K6 ๋ถํ ํ ์คํธ๋ฅผ ์ํํ์์ต๋๋ค.
ํ ์คํธ ๊ฒฐ๊ณผ, ์ฝ 3.5K VUs๊น์ง๋ RPS๊ฐ ์ง์์ ์ผ๋ก ์ฆ๊ฐํ์ง๋ง, ๊ทธ ์ดํ๋ถํฐ๋ ๋ ์ด์ ์ฆ๊ฐํ์ง ์๊ณ ํฌํ ์ํ์ ๋๋ฌํ๋ ์์์ด ๊ด์ธก๋์์ต๋๋ค. ์ด์ ๋ฐ๋ผ ์ต๋ ๋ถํ ์ง์ ์ 3.5K VUs ์์ค์ผ๋ก ํ๋จํ๊ณ ํด๋น ์งํ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ธก์ ํ์์ต๋๋ค.
ํด๋น ๊ฐ์ ์์ ์ ํตํด, ์ด์ ์ ๋ฐ์ํ๋ ์ฌ๊ฐํ ์ฑ๋ฅ ์ ํ ๋ฌธ์ ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ํด๊ฒฐํ์์ผ๋ฉฐ, 3,500 VUs๊น์ง ์์ ์ ์ธ RPS ์ฆ๊ฐ์ ํจ๊ป ํ๊ท ์๋ต ์๊ฐ 99.95% ๋จ์ถ์ด๋ผ๋ ์ฑ๊ณผ๋ฅผ ๋ฌ์ฑํ์์ต๋๋ค.
๋ํ, ํ๊ท QPS๋ ๊ธฐ์กด 8.75์์ 15.5K๋ก ์ฝ 1,771๋ฐฐ ์ฆ๊ฐํ์์ผ๋ฉฐ, ๋ ๋ง์ ์์ ์ ์ฒ๋ฆฌํ๋ฉด์๋ CPU ์ต๋ ์ฌ์ฉ๋ฅ ์ด 92.9%์์ 23.1%๋ก ํฌ๊ฒ ๊ฐ์ํ์์ต๋๋ค. ์ฌ๊ธฐ์ ๋ํด ์ค๋ฅ์จ 0%๋ฅผ ์ ์งํจ์ผ๋ก์จ, ์๋น์ค์ ์ฒ๋ฆฌ๋, ์์ ์ฑ, ์์ ํจ์จ์ฑ ๋ชจ๋๊ฐ ํฌ๊ฒ ํฅ์๋์์์ ํ์ธํ ์ ์์์ต๋๋ค.
'Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
GitHub Actions + Docker + NGINX๋ฅผ ํ์ฉํ Blue/Green ๋ฌด์ค๋จ ๋ฐฐํฌ ํ๊ฒฝ ๊ตฌ์ถ (1) | 2025.05.04 |
---|---|
์ฑํ ๋ฉ์ธ์ง ์ฝ์ ์ฒ๋ฆฌ ๊ธฐ๋ฅ ๊ตฌ์กฐ ๊ฐ์ ๊ธฐ (0) | 2024.12.18 |
Embedded Mongo/Redis ์ ์ฉํ๊ธฐ (0) | 2024.11.26 |
๋ณ์ ์ค๋ณต ์์ฑ ๋์์ฑ ์ด์ ํด๊ฒฐ (0) | 2024.10.27 |
CompletableFuture๋ฅผ ํ์ฉํ ์ฑ๋ฅ ๊ฐ์ ๊ธฐ (0) | 2024.08.01 |