-
Notifications
You must be signed in to change notification settings - Fork 0
[refactor] 게시글 좋아요 api 서버 처리율 개선 - Redis 원자적 연산 적용 #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hd0rable
wants to merge
35
commits into
develop
Choose a base branch
from
test/#322-k6-feed-like-redis-INCR
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
5ac36e6
[test] k6 테스트파일 추가 (#322)
hd0rable 480e850
[test] 피드 좋아요 상태변경 다중 스레드 테스트 (#322)
hd0rable f14a490
[test] 피드 좋아요 상태변경 다중 스레드 테스트코드 수정 (#322)
hd0rable 1731bcc
[test] k6 테스트파일 추가 (#322)
hd0rable 20c2b95
[test] 피드 좋아요 상태변경 다중 스레드 테스트코드 수정 (#322)
hd0rable 74aa272
[fix] 게시글 좋아요 상태변경 s-lock에서 x-lock으로 승격할때 데드락 상황을 해결하기위해 서비스로직 순서 변경 …
hd0rable be10bed
[chore] 안쓰는 테스트코드 스크릾트 삭제 (#338)
hd0rable b3b4655
[test] 특정 시점에 한 게시물 (인기 작가,인플루언서가 작성한)에 좋아요 요청이 몰리는 시나리오 k6 테스트 스크립트 …
hd0rable a9b91ca
[refactor] 게시글 공통인터페이스에 getLikeCount() 함수 추가 (#338)
hd0rable a34c050
[refactor] 레디스 도입시 게시글 도메인이 좋아요 업데이트 검증시에 likeCount 외부에서 주입받도록 수정 (#338)
hd0rable 5dfae3b
[refactor] feed findPostIdsByIds,batchUpdateLikeCounts 추가 (#338)
hd0rable f4d8041
[feat] FeedJpaRepository.findByPostIds 추가 (#338)
hd0rable 36d5524
[feat] FeedCommandPersistenceAdapter.findByIds() 추가 및 batchUpdateLik…
hd0rable bc6b49e
[feat] PostHandler
hd0rable fcf3e5c
[feat] 게시글 좋아요 캐싱 PostLikeCountRedisCommand,Query Port 추가 (#338)
hd0rable 7302cc3
[feat] 게시글 좋아요 캐싱 구현체 PostLikeCountRedisAdapter 추가 (#338)
hd0rable 5a9cc95
[feat] RecordCommandPersistenceAdapterAdapter.findByIds() 추가 및 batch…
hd0rable 4154b49
[refactor] RecordCommandPort findPostIdsByIds,batchUpdateLikeCounts 추…
hd0rable 3e7a47c
[feat] RecordJpaRepository.findByPostIds 추가 (#338)
hd0rable 7390280
[feat] RedisConfig.redisIntegerTemplate 추가 (#338)
hd0rable 567f1e6
[feat] VoteCommandPersistenceAdapter.findByIds() 추가 및 batchUpdateLik…
hd0rable c972f51
[refactor] VoteCommandPort.findPostIdsByIds,batchUpdateLikeCounts 추가 …
hd0rable 35f3456
[feat] VoteJpaRepository.findByPostIds 추가 (#338)
hd0rable c6401d7
[refactor] 게시글 좋아요 시, post 최초 조회시에 비관락 해제하고 레디스의 INCR/DECR 명령어를 사용하는 …
hd0rable cb00e01
[feat]
hd0rable e4891a4
[chore] 주석 정리 (#332)
hd0rable f6b6ba9
[fix] 쿼리 실행 전 빈 목록 파라미터 유효성 검사 추가 (#324)
hd0rable f4d3a2a
[refactor] 실제 존재하는 ID가 없을 때 DB 업데이트 방지 (#324)
hd0rable bb2a260
[test] 게시글 좋아요 로직 변경에 따른 테스트 코드 수정
hd0rable 59f89c2
[test] 게시글 좋아요 로직 변경에 따른 게시글(도메인) 테스트 코드 수정
hd0rable 9011584
[refactor] db 동기화 스케줄러 test환경에서는 실행 안되도록 수정 (#338)
hd0rable a6cdc57
[test] book 중복 insert 테스트 깨지는 거 수정 (#338)
hd0rable 02794c2
[test] 캐싱 로직 추가로 레디스 초기화하는 셋업 처리 추가 (#338)
hd0rable 17f23d3
[chore] import문 정리 (#338)
hd0rable 38bfd73
Merge branch 'develop' into test/#322-k6-feed-like-redis-INCR
hd0rable File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| // feed-like-load-test.js | ||
| import http from 'k6/http'; | ||
| import { sleep,check } from 'k6'; | ||
| import { Trend, Counter } from 'k6/metrics'; | ||
|
|
||
| const BASE_URL = 'http://localhost:8080'; | ||
| const FEED_ID = 1; // 테스트할 피드 ID | ||
| const USERS_START = 1; // 토큰 발급 시작 userId | ||
| const USERS_COUNT = 5000; // 총 사용자 = VU 수 | ||
| const TOKEN_BATCH = 200; // 토큰 발급 배치 크기 | ||
| const BATCH_PAUSE_S = 0.2; // 배치 간 대기 (for 토큰 발급 API 병목 방지) | ||
| const START_DELAY_S = 5; // 테스트 시작 전 대기 (for 방 참여 요청 동시 시작) | ||
|
|
||
| // ===== 커스텀 메트릭 ===== | ||
| const likeLatency = new Trend('feed_like_latency'); // 참여 API 지연(ms) | ||
| const http5xx = new Counter('feed_like_5xx'); // 5xx 개수 | ||
| const http2xx = new Counter('feed_like_2xx'); // 2xx 개수 | ||
| const http4xx = new Counter('feed_like_4xx'); // 4xx 개수 | ||
|
|
||
| // 실패 원인 분포 파악용(응답 JSON의 code 필드 기준) | ||
| const token_issue_failed = new Counter('token_issue_failed'); | ||
| const fail_POST_ALREADY_LIKED = new Counter('fail_POST_ALREADY_LIKED'); | ||
| const fail_POST_NOT_LIKED_CANNOT_CANCEL = new Counter('fail_POST_NOT_LIKED_CANNOT_CANCEL'); | ||
| const fail_POST_LIKE_COUNT_UNDERFLOW = new Counter('fail_POST_LIKE_COUNT_UNDERFLOW'); | ||
| const fail_OTHER_4XX = new Counter('fail_OTHER_4XX'); | ||
|
|
||
| const ERR = { // THIP error code | ||
| POST_ALREADY_LIKED: 185001, | ||
| POST_NOT_LIKED_CANNOT_CANCEL: 185002, | ||
| POST_LIKE_COUNT_UNDERFLOW: 185000 | ||
| }; | ||
|
|
||
| function parseError(res) { | ||
| try { | ||
| const j = JSON.parse(res.body || '{}'); // BaseResponse 구조 | ||
| return { | ||
| code: Number(j.code), // 정수 코드 | ||
| message: j.message || '', | ||
| requestId: j.requestId || '', | ||
| isSuccess: !!j.isSuccess | ||
| }; | ||
| } catch (e) { | ||
| return { code: NaN, message: '', requestId: '', isSuccess: false }; | ||
| } | ||
| } | ||
|
|
||
| // ------------ 시나리오 ------------ | ||
| // 특정 시점에 한 게시물 (인기 작가,인플루언서가 작성한)에 좋아요 요청이 몰리는 상황 가정 | ||
| export let options = { | ||
| scenarios: { | ||
| // 각 VU가 "정확히 1회" 실행 → 1 VU = 1명 유저 | ||
| feed_like_once: { | ||
| executor: 'per-vu-iterations', | ||
| vus: USERS_COUNT, | ||
| iterations: 1, | ||
| startTime: '0s', // 모든 VU가 거의 동시에 스케줄링 | ||
| gracefulStop: '5s', | ||
| }, | ||
| }, | ||
| thresholds: { | ||
| feed_like_5xx: ['count==0'], // 서버 오류는 0건이어야 함 | ||
| feed_like_latency: ['p(95)<500'], // p95 < 500ms | ||
| }, | ||
| }; | ||
|
|
||
| // 테스트 전 사용자 별 토큰 배치 발급 | ||
| export function setup() { | ||
| const userIds = Array.from({ length: USERS_COUNT }, (_, i) => USERS_START + i); | ||
| const tokens = []; | ||
|
|
||
| for (let i = 0; i < userIds.length; i += TOKEN_BATCH) { | ||
| const slice = userIds.slice(i, i + TOKEN_BATCH); | ||
| const reqs = slice.map((uid) => [ | ||
| 'GET', | ||
| `${BASE_URL}/api/test/token/access?userId=${uid}`, | ||
| null, | ||
| { tags: { phase: 'setup_token_issue', feed: `${FEED_ID}` } }, | ||
| ]); | ||
|
|
||
| const responses = http.batch(reqs); | ||
| for (const r of responses) { | ||
| if (r.status === 200 && r.body) { | ||
| tokens.push(r.body.trim()); | ||
| } | ||
| else { | ||
| tokens.push(''); // 실패한 자리도 인덱스 유지 | ||
| token_issue_failed.add(1); | ||
| } | ||
| } | ||
| sleep(BATCH_PAUSE_S); | ||
| } | ||
| if (tokens.length > USERS_COUNT) tokens.length = USERS_COUNT; | ||
|
|
||
| const startAt = Date.now() + START_DELAY_S * 1000; // 동시 시작 시간 | ||
|
|
||
| return { tokens, startAt }; | ||
| } | ||
|
|
||
| // VU : 각자 자기 토큰으로 참여 호출 & 각자 1회만 실행 | ||
| export default function (data) { | ||
| const vuIdx = __VU - 1; | ||
| const token = data.tokens[vuIdx]; | ||
|
|
||
| // 동기 시작: startAt까지 대기 → 모든 VU가 거의 같은 타이밍에 시작 | ||
| const now = Date.now(); | ||
| if (now < data.startAt) { | ||
| sleep((data.startAt - now) / 1000); | ||
| } | ||
|
|
||
| if (!token) { // 토큰 발급 실패 -> 스킵 | ||
| return; | ||
| } | ||
|
|
||
| const headers = { | ||
| Authorization: `Bearer ${token}`, | ||
| 'Content-Type': 'application/json', | ||
| }; | ||
|
|
||
| // 동시에 모든 유저가 인기 게시물에 대해 좋아요 요청 | ||
| const body = JSON.stringify({ type: 'true' }); | ||
| const url = `${BASE_URL}/feeds/${FEED_ID}/likes`; | ||
|
|
||
| const res = http.post(url, body, { headers, tags: { phase: 'like', feed: `${FEED_ID}` } }); | ||
|
|
||
| // === 커스텀 메트릭 기록 === | ||
| likeLatency.add(res.timings.duration); | ||
| if (res.status >= 200 && res.status < 300) http2xx.add(1); | ||
| else if (res.status >= 400 && res.status < 500) { | ||
| http4xx.add(1); | ||
| const err = parseError(res); | ||
| switch (err.code) { | ||
| case ERR.POST_ALREADY_LIKED: | ||
| fail_POST_ALREADY_LIKED.add(1); | ||
| break; | ||
| case ERR.POST_NOT_LIKED_CANNOT_CANCEL: | ||
| fail_POST_NOT_LIKED_CANNOT_CANCEL.add(1); | ||
| break; | ||
| case ERR.POST_LIKE_COUNT_UNDERFLOW: | ||
| fail_POST_LIKE_COUNT_UNDERFLOW.add(1); | ||
| break; | ||
| default: | ||
| fail_OTHER_4XX.add(1); | ||
| } | ||
| } else if (res.status >= 500) { | ||
| http5xx.add(1); | ||
| } | ||
|
|
||
| // === 검증 === | ||
| check(res, { | ||
| 'like responded': (r) => r.status !== 0, | ||
| 'like 200 or expected 4xx': (r) => r.status === 200 || (r.status >= 400 && r.status < 500), | ||
| }); | ||
| } | ||
|
|
||
| // 테스트 결과 html 리포트로 저장 | ||
| import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js"; | ||
| export function handleSummary(data) { | ||
| return { | ||
| "summary.html": htmlReport(data), | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.