-
Notifications
You must be signed in to change notification settings - Fork 0
[test] 모임방 참여 api 부하 테스트 진행 #329
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| import http from 'k6/http'; | ||
| import { check, sleep } from 'k6'; | ||
| import { Trend, Counter } from 'k6/metrics'; | ||
|
|
||
| const BASE_URL = 'http://localhost:8080'; | ||
| const ROOM_ID = 12345; | ||
| const USERS_START = 10000; // 토큰 발급 시작 userId | ||
| const USERS_COUNT = 500; // 총 사용자 = VU 수 | ||
| const TOKEN_BATCH = 200; // 토큰 발급 배치 크기 | ||
| const BATCH_PAUSE_S = 0.2; // 배치 간 대기 (for 토큰 발급 API 병목 방지) | ||
| const START_DELAY_S = 5; // 테스트 시작 전 대기 (for 방 참여 요청 동시 시작) | ||
|
|
||
| // ===== 커스텀 메트릭 ===== | ||
| const joinLatency = new Trend('rooms_join_latency'); // 참여 API 지연(ms) | ||
| const http5xx = new Counter('rooms_join_5xx'); // 5xx 개수 | ||
| const http2xx = new Counter('rooms_join_2xx'); // 2xx 개수 | ||
| const http4xx = new Counter('rooms_join_4xx'); // 4xx 개수 | ||
|
|
||
| // 실패 원인 분포 파악용(응답 JSON의 code 필드 기준) | ||
| const token_issue_failed = new Counter('token_issue_failed'); | ||
| const fail_ROOM_MEMBER_COUNT_EXCEEDED = new Counter('fail_ROOM_MEMBER_COUNT_EXCEEDED'); | ||
| const fail_USER_ALREADY_PARTICIPATE = new Counter('fail_USER_ALREADY_PARTICIPATE'); | ||
| const fail_OTHER_4XX = new Counter('fail_OTHER_4XX'); | ||
|
|
||
| const ERR = { // THIP error code | ||
| ROOM_MEMBER_COUNT_EXCEEDED: 100006, | ||
| USER_ALREADY_PARTICIPATE: 140005, | ||
| }; | ||
|
|
||
| function parseError(res) { | ||
| try { | ||
| const j = JSON.parse(res.body || '{}'); // BaseResponse 구조 | ||
| // BaseResponse: { isSuccess:boolean, code:number, message:string, requestId:string, data:any } | ||
| return { | ||
| code: Number(j.code), // 정수 코드 | ||
| message: j.message || '', | ||
| requestId: j.requestId || '', | ||
| isSuccess: !!j.isSuccess | ||
| }; | ||
| } catch (e) { | ||
| return { code: NaN, message: '', requestId: '', isSuccess: false }; | ||
| } | ||
| } | ||
|
|
||
| // ------------ 시나리오 ------------ | ||
| // [인기 작가가 만든 모임방에 THIP의 수많은 유저들이 '모임방 참여' 요청을 보내는 상황 가정] | ||
| export const options = { | ||
| scenarios: { | ||
| // 각 VU가 "정확히 1회" 실행 → 1 VU = 1명 유저 | ||
| join_once_burst: { | ||
| executor: 'per-vu-iterations', | ||
| vus: USERS_COUNT, | ||
| iterations: 1, | ||
| startTime: '0s', // 모든 VU가 거의 동시에 스케줄링 | ||
| gracefulStop: '5s', | ||
| }, | ||
| }, | ||
| thresholds: { | ||
| rooms_join_5xx: ['count==0'], // 서버 오류는 0건이어야 함 | ||
| rooms_join_latency: ['p(95)<1000'], // p95 < 1s | ||
| }, | ||
| }; | ||
|
|
||
| // setup: 토큰 배치 발급 | ||
| // roomId 12345 방 & userId 10000 ~ 유저들은 사전에 만들어져 있어야 함 | ||
| 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', room: `${ROOM_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회만 실행 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 이렇게 진짜 어느 특정시점에 방참여가 몰리는 부하테스트를 실행하신거군요 좋네요 |
||
| export default function (data) { | ||
| const idx = __VU - 1; // VU <-> user 매핑(1:1) | ||
| const token = data.tokens[idx]; | ||
|
|
||
| // 동기 시작: 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: 'join' }); | ||
| const url = `${BASE_URL}/rooms/${ROOM_ID}/join`; | ||
|
|
||
| const res = http.post(url, body, { headers, tags: { phase: 'join', room: `${ROOM_ID}` } }); | ||
|
|
||
| // === 커스텀 메트릭 기록 === | ||
| joinLatency.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.ROOM_MEMBER_COUNT_EXCEEDED: | ||
| fail_ROOM_MEMBER_COUNT_EXCEEDED.add(1); | ||
| break; | ||
| case ERR.USER_ALREADY_PARTICIPATE: | ||
| fail_USER_ALREADY_PARTICIPATE.add(1); | ||
| break; | ||
| default: | ||
| fail_OTHER_4XX.add(1); | ||
| } | ||
| } else if (res.status >= 500) { | ||
| http5xx.add(1); | ||
| } | ||
|
|
||
| // === 검증 === | ||
| check(res, { | ||
| 'join responded': (r) => r.status !== 0, | ||
| 'join 200 or expected 4xx': (r) => r.status === 200 || (r.status >= 400 && r.status < 500), | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,150 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package konkuk.thip.room.concurrency; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.util.TestEntityFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.adapter.in.web.request.RoomJoinRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.domain.value.Category; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.domain.value.RoomParticipantRole; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.user.domain.value.Alias; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.DisplayName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.Tag; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.Test; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.jdbc.core.JdbcTemplate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.test.context.ActiveProfiles; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.test.web.servlet.MockMvc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.*; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @SpringBootTest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ActiveProfiles("test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AutoConfigureMockMvc(addFilters = false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @DisplayName("[동시성] 방 참여 동시 요청 테스트") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Tag("concurrency") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class RoomJoinConcurrencyTest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private MockMvc mockMvc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private ObjectMapper objectMapper; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private RoomJpaRepository roomJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private BookJpaRepository bookJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private UserJpaRepository userJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Autowired private JdbcTemplate jdbcTemplate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @DisplayName("[동시성] 방 참여 동시 요청 테스트") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이테스트 h2환경에서 실행해도 데드락 상황이 로그에 남나요??
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아니요 테스트 코드에서 명확한 500 error 관련 로그를 확인하지는 못했습니다!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 테스트 코드로는 데이터 정합성만 판단하고 정확한 에러율과 에러 메시지는 MySQL을 사용하여 부하 테스트로 확인하는 것으로 이해했는데 맞을까요?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
넵 맞습니다! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void room_join_test_in_multi_thread() throws Exception { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //given | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int requestUserCount = 500; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 모집인원 10명 방 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RoomJpaEntity room = roomJpaRepository.save(TestEntityFactory.createCustomRoom(book, Category.LITERATURE, 10)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Long> savedUserIds = createUsersRange(requestUserCount + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UserJpaEntity hostUser = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, hostUser, RoomParticipantRole.HOST, 0.0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Long> requestUserIds = savedUserIds.subList(1, savedUserIds.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //when : 동시에 방 참여 요청 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExecutorService pool = Executors.newFixedThreadPool(Math.min(requestUserCount, 100)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CountDownLatch ready = new CountDownLatch(requestUserCount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CountDownLatch start = new CountDownLatch(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CountDownLatch finish = new CountDownLatch(requestUserCount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RoomJoinRequest body = new RoomJoinRequest("join"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String json = objectMapper.writeValueAsString(body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Future<Integer>> results = new ArrayList<>(requestUserCount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i < requestUserCount; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final long userId = requestUserIds.get(i); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| results.add(pool.submit(() -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ready.countDown(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start.await(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mockMvc.perform(post("/rooms/{roomId}/join", room.getRoomId()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .contentType("application/json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .content(json) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .requestAttr("userId", userId)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .andExpect(status().isOk()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 200; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (AssertionError e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 400; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finish.countDown(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 동시에 시작 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ready.await(10, TimeUnit.SECONDS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start.countDown(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finish.await(60, TimeUnit.SECONDS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pool.shutdown(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동시성 동기화 로직은 적절하나, 타임아웃 처리 개선이 필요합니다. CountDownLatch를 활용한 동기화 패턴은 정확하나, 다음 사항들을 고려해야 합니다:
다음과 같이 개선할 수 있습니다: // 동시에 시작
-ready.await(10, TimeUnit.SECONDS);
+boolean allReady = ready.await(10, TimeUnit.SECONDS);
+assertThat(allReady).isTrue();
start.countDown();
-finish.await(60, TimeUnit.SECONDS);
+boolean allFinished = finish.await(60, TimeUnit.SECONDS);
+assertThat(allFinished).isTrue();
pool.shutdown();예외 처리 개선: try {
mockMvc.perform(post("/rooms/{roomId}/join", room.getRoomId())
.contentType("application/json")
.content(json)
.requestAttr("userId", userId))
.andExpect(status().isOk());
return 200;
} catch (AssertionError e) {
return 400;
+ } catch (Exception e) {
+ return 500;
} finally {
finish.countDown();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long okCount = results.stream().filter(f -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return f.get() == 200; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).count(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //then : DB 실측값 검증 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RoomJpaEntity reloadedRoom = roomJpaRepository.findByRoomId(room.getRoomId()).orElseThrow(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long participantRows = jdbcTemplate.query( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "SELECT COUNT(*) FROM room_participants WHERE room_id = ?", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ps -> ps.setLong(1, room.getRoomId()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rs -> {rs.next(); return rs.getLong(1); } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+112
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major JDBC 쿼리 패턴이 비정상적입니다.
더 안전하고 명확한 방법을 사용하세요: -long participantRows = jdbcTemplate.query(
- "SELECT COUNT(*) FROM room_participants WHERE room_id = ?",
- ps -> ps.setLong(1, room.getRoomId()),
- rs -> {rs.next(); return rs.getLong(1); }
-);
+Long participantRows = jdbcTemplate.queryForObject(
+ "SELECT COUNT(*) FROM room_participants WHERE room_id = ?",
+ Long.class,
+ room.getRoomId()
+);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int memberCountInRoom = reloadedRoom.getMemberCount(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int recruit = reloadedRoom.getRecruitCount(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.out.println("=== RESULT ==="); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.out.println("OK responses(= 방 참여 OK 응답을 받은 thread 수) : " + okCount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.out.println("participants rows(= host 1명 포함된 결과) : " + participantRows); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.out.println("room.memberCount : " + memberCountInRoom); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.out.println("recruitCount : " + recruit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 현재 방 참여 로직 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 1. 방 참여자에 해당하는 RoomParticipant 엔티티 저장 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 2. Room 엔티티의 memberCount 증가 (Room의 도메인 규칙으로 memberCount가 recruitCount를 초과하지 않도록 제한됨) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * -> 동시성 경쟁이 발생하는 트래픽 규모에서는 participants 테이블의 행 수와 Room.memberCount 동기화 보장 X | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1) participants가 recruitCount 보다 커질 수 있음 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assertThat(participantRows).isGreaterThan(recruit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2) memberCount가 실제 participants 수보다 작을 수 있음 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // memberCount 값은 Room 도메인 규칙에 의해 recruitCount를 초과하여 증가하지 않음 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assertThat(memberCountInRoom).isLessThan((int) participantRows); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+134
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 시나리오에서는 다수의 사용자가 동시에 동일한 방에 참여하려고 할 때, 성준님께서 정리해주신 노션 내용을 참고했을 때, 데드락이 발생하면 하나의 트랜잭션은 롤백되고 다른 하나의 트랜잭션만 UPDATE 쿼리가 반영되는 것으로 이해했습니다.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 노션에 희진님께서 동일한 내용을 질문해주셔서 관련해서 댓글 단 내용 참고해주시면 될 것 같습니다! 저는 데드락 상황이 발생하면 MySQL이 트랜잭션 하나를 롤백시키는게 맞지만, 데드락 상황이 발생하지 않는 경우에는 두 트랜잭션이 서로 rooms 레코드의 memberCount 의 값을 동일하게 update 시키고 commit 하므로 데이터 정합성이 깨질 수 있다(= 방의 멤버 수와 방 참여자 의 전체 수가 다를 수 있다) 라고 생각합니다 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private List<Long> createUsersRange(long count) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Long> userIds = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (long i = 0; i < count; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UserJpaEntity saved = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userIds.add(saved.getUserId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return userIds; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 좋습니닷 👍🏻👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 이렇게하면 깃허브 액션에서 CI 작업시에 제외시켜 주는건가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CI, CD 시에 해당 테스트 코드는 제외하기 위해서 찾아봤는데 이런 방법이 있는거 같아서 적용하였습니다.
다만 실제로 잘 동작하는지는 확실하진 않습니다. 좀 더 알아보겠슴다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai 내가 적용한 방식으로 CI 시에 해당 테스트 메서드 클래스를 제외할 수 있는게 맞나?? 그리고 실제로 잘 적용되었는지를 확인하기 위한 방법이 뭐가 있을까?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 2289
🏁 Script executed:
Length of output: 22
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 341
🏁 Script executed:
Length of output: 302
🏁 Script executed:
Length of output: 32
🏁 Script executed:
Length of output: 2057
🏁 Script executed:
Length of output: 5559
@seongjunnoh
네, 적용한 방식이 맞습니다! 확인 결과:
✅ 설정이 올바른 이유
RoomJoinConcurrencyTest에@Tag("concurrency")가 38번째 줄에 정확히 적용되어 있습니다excludeTags사용이 올바릅니다CI=true환경변수를 자동으로 설정합니다🔍 동작 확인 방법
1. GitHub Actions 로그 확인 (가장 확실한 방법)
PR을 올리면 CI 워크플로우가 실행되는데, Actions 탭에서 로그를 확인하세요:
또는 전체 테스트 수가 1개 줄어든 것을 확인할 수 있습니다.
2. 로컬에서 테스트
3. 임시 확인 코드 추가
build.gradle에 임시로 로그를 추가해서 확인:
💡 추가 개선 제안
명시성을 위해
.github/workflows/ci-workflow.yml의env섹션에CI: true를 명시적으로 추가하는 것도 좋습니다 (GitHub Actions가 자동으로 설정하지만, 코드로 명확히 표현):다음 PR CI 실행 시 Actions 로그에서 테스트 수를 확인해보시면
RoomJoinConcurrencyTest가 제외된 것을 확인하실 수 있을 겁니다!🧠 Learnings used