Skip to content

Commit

Permalink
Merge pull request #45 from softeerbootcamp4th/feature/44-fix-test
Browse files Browse the repository at this point in the history
[chore] 선착순 이벤트 부하 테스트 관련 일부 수정 (#44)
  • Loading branch information
win-luck authored Aug 12, 2024
2 parents 29df496 + 91e9996 commit 0335042
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ public class DbFcfsService implements FcfsService{
@Override
@Transactional
public boolean participate(Long eventSequence, String userId){
String key = eventSequence.toString();
// 이벤트 종료 여부 확인
if (isEventEnded(eventSequence)) {
if (isEventEnded(key)) {
return false;
}

Expand All @@ -53,23 +54,25 @@ public boolean participate(Long eventSequence, String userId){

// 인원 수 초과 시 종료 flag 설정
if(fcfsEvent.getInfos().size() >= fcfsEvent.getParticipantCount()){
endEvent(eventSequence);
log.info("Event Finished: {},", fcfsEvent.getInfos().size());
endEvent(key);
return false;
}

fcfsEventWinningInfoRepository.save(FcfsEventWinningInfo.of(fcfsEvent, eventUser));
log.info("Participating Success: {}, User ID: {}", eventSequence, userId);
return true;
}

private boolean isParticipated(Long userId, Long eventSequence){
return fcfsEventWinningInfoRepository.existsByEventUserIdAndFcfsEventId(userId, eventSequence);
}

private boolean isEventEnded(Long eventSequence){
return Boolean.TRUE.equals((booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(eventSequence.toString()))));
private boolean isEventEnded(String key){
return Boolean.TRUE.equals((booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(key))));
}

private void endEvent(Long eventSequence){
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(eventSequence.toString()), true);
private void endEvent(String key){
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(key), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
import hyundai.softeer.orange.event.fcfs.exception.FcfsEventException;
import hyundai.softeer.orange.event.fcfs.util.FcfsUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@RequiredArgsConstructor
@Service
public class FcfsAnswerService {
Expand All @@ -20,6 +18,6 @@ public boolean judgeAnswer(Long eventSequence, String answer) {
if (correctAnswer == null) {
throw new FcfsEventException(ErrorCode.FCFS_EVENT_NOT_FOUND);
}
return correctAnswer.equals(answer);
return correctAnswer.trim().equals(answer.trim()); // 정답 비교 시 공백을 제거
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,15 @@ public List<ResponseFcfsWinnerDto> getFcfsWinnersInfo(Long eventSequence) {
}

private void prepareEventInfo(FcfsEvent event) {
numberRedisTemplate.opsForValue().set(FcfsUtil.keyFormatting(event.getId().toString()), event.getParticipantCount().intValue());
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(event.getId().toString()), false);
stringRedisTemplate.opsForValue().set(FcfsUtil.startTimeFormatting(event.getId().toString()), event.getStartTime().toString());
String key = event.getId().toString();
numberRedisTemplate.opsForValue().set(FcfsUtil.keyFormatting(key), event.getParticipantCount().intValue());
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(key), false);
stringRedisTemplate.opsForValue().set(FcfsUtil.startTimeFormatting(key), event.getStartTime().toString());

// FIXME: 선착순 정답 생성 과정을 별도로 관리하는 것이 좋을 듯
// 현재 정책 상 1~4 중 하나의 숫자를 선정하여 현재 선착순 이벤트의 정답에 저장
int answer = new Random().nextInt(4) + 1;
stringRedisTemplate.opsForValue().set(FcfsUtil.answerFormatting(event.getId().toString()), String.valueOf(answer));
stringRedisTemplate.opsForValue().set(FcfsUtil.answerFormatting(key), String.valueOf(answer));
}

public void deleteEventInfo(String eventId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
Expand All @@ -18,7 +17,6 @@
@Slf4j
@RequiredArgsConstructor
@Service
@Primary
public class RedisLockFcfsService implements FcfsService {

private final StringRedisTemplate stringRedisTemplate;
Expand All @@ -28,19 +26,20 @@ public class RedisLockFcfsService implements FcfsService {

@Override
public boolean participate(Long eventSequence, String userId) {
String key = eventSequence.toString();
// 이벤트 종료 여부 확인
if (isEventEnded(eventSequence)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
if (isEventEnded(key)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
return false;
}

// 이미 이 이벤트에 참여했는지 확인
if(isParticipated(eventSequence, userId)) {
if(isParticipated(key, userId)) {
throw new FcfsEventException(ErrorCode.ALREADY_PARTICIPATED);
}

// 잘못된 이벤트 참여 시간
String startTime = stringRedisTemplate.opsForValue().get(FcfsUtil.startTimeFormatting(eventSequence.toString()));
String startTime = stringRedisTemplate.opsForValue().get(FcfsUtil.startTimeFormatting(key));
if(startTime == null) {
throw new FcfsEventException(ErrorCode.FCFS_EVENT_NOT_FOUND);
}
Expand All @@ -57,15 +56,17 @@ public boolean participate(Long eventSequence, String userId) {
return false;
}

int quantity = availableCoupons(FcfsUtil.keyFormatting(eventSequence.toString()));
int quantity = availableCoupons(FcfsUtil.keyFormatting(key));
if (quantity <= 0) {
endEvent(eventSequence); // 이벤트 종료 플래그 설정
log.info("Event Finished: {},", stringRedisTemplate.opsForZSet().zCard(FcfsUtil.winnerFormatting(key)));
endEvent(key); // 이벤트 종료 플래그 설정
return false;
}

numberRedisTemplate.opsForValue().decrement(FcfsUtil.keyFormatting(eventSequence.toString()));
stringRedisTemplate.opsForZSet().add(FcfsUtil.winnerFormatting(eventSequence.toString()), userId, System.currentTimeMillis());
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
numberRedisTemplate.opsForValue().decrement(FcfsUtil.keyFormatting(key));
stringRedisTemplate.opsForZSet().add(FcfsUtil.winnerFormatting(key), userId, System.currentTimeMillis());
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
log.info("Participating Success: {}, User ID: {}", eventSequence, userId);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Expand All @@ -77,8 +78,8 @@ public boolean participate(Long eventSequence, String userId) {
}
}

private boolean isParticipated(Long eventSequence, String userId){
return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(FcfsUtil.participantFormatting(eventSequence.toString()), userId));
private boolean isParticipated(String key, String userId){
return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(FcfsUtil.participantFormatting(key), userId));
}

private Integer availableCoupons(String key) {
Expand All @@ -89,11 +90,11 @@ private Integer availableCoupons(String key) {
return count;
}

private boolean isEventEnded(Long eventSequence) {
return Boolean.TRUE.equals(booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(eventSequence.toString())));
private boolean isEventEnded(String key) {
return Boolean.TRUE.equals(booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(key)));
}

private void endEvent(Long eventSequence) {
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(eventSequence.toString()), true);
private void endEvent(String key) {
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(key), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import hyundai.softeer.orange.event.fcfs.util.FcfsUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
Expand All @@ -15,6 +16,7 @@

@Slf4j
@RequiredArgsConstructor
@Primary
@Service
public class RedisLuaFcfsService implements FcfsService {

Expand All @@ -24,19 +26,20 @@ public class RedisLuaFcfsService implements FcfsService {

@Override
public boolean participate(Long eventSequence, String userId) {
String key = eventSequence.toString();
// 이벤트 종료 여부 확인
if (isEventEnded(eventSequence)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
if (isEventEnded(key)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
return false;
}

// 이미 이 이벤트에 참여했는지 확인
if(isParticipated(eventSequence, userId)) {
if(isParticipated(key, userId)) {
throw new FcfsEventException(ErrorCode.ALREADY_PARTICIPATED);
}

// 잘못된 이벤트 참여 시간
String startTime = stringRedisTemplate.opsForValue().get(FcfsUtil.startTimeFormatting(eventSequence.toString()));
String startTime = stringRedisTemplate.opsForValue().get(FcfsUtil.startTimeFormatting(key));
if(startTime == null) {
throw new FcfsEventException(ErrorCode.FCFS_EVENT_NOT_FOUND);
}
Expand All @@ -55,33 +58,34 @@ public boolean participate(Long eventSequence, String userId) {
long timestamp = System.currentTimeMillis();
Long result = stringRedisTemplate.execute(
RedisScript.of(script, Long.class),
Collections.singletonList(FcfsUtil.winnerFormatting(eventSequence.toString())),
String.valueOf(numberRedisTemplate.opsForValue().get(FcfsUtil.keyFormatting(eventSequence.toString()))),
Collections.singletonList(FcfsUtil.winnerFormatting(key)),
String.valueOf(numberRedisTemplate.opsForValue().get(FcfsUtil.keyFormatting(key))),
String.valueOf(timestamp),
userId
);

if(result == null || result <= 0) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
endEvent(eventSequence);
log.info("Event Finished: {},", stringRedisTemplate.opsForZSet().zCard(FcfsUtil.winnerFormatting(key)));
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
endEvent(key);
return false;
}

stringRedisTemplate.opsForZSet().add(FcfsUtil.winnerFormatting(eventSequence.toString()), userId, System.currentTimeMillis());
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
log.info("Event Sequence: {}, User ID: {}, Timestamp: {}", eventSequence, userId, timestamp);
stringRedisTemplate.opsForZSet().add(FcfsUtil.winnerFormatting(key), userId, System.currentTimeMillis());
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
log.info("Participating Success: {}, User ID: {}, Timestamp: {}", eventSequence, userId, timestamp);
return true;
}

public boolean isParticipated(Long eventSequence, String userId) {
return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(FcfsUtil.participantFormatting(eventSequence.toString()), userId));
public boolean isParticipated(String key, String userId) {
return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(FcfsUtil.participantFormatting(key), userId));
}

private boolean isEventEnded(Long eventSequence) {
return Boolean.TRUE.equals(booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(eventSequence.toString())));
private boolean isEventEnded(String key) {
return Boolean.TRUE.equals(booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(key)));
}

private void endEvent(Long eventSequence) {
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(eventSequence.toString()), true);
private void endEvent(String key) {
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(key), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@ public class RedisSetFcfsService implements FcfsService {

@Override
public boolean participate(Long eventSequence, String userId) {
String key = eventSequence.toString();
// 이벤트 종료 여부 확인
if(isEventEnded(eventSequence)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
if(isEventEnded(key)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
return false;
}

// 이미 이 이벤트에 참여했는지 확인
if(isParticipated(eventSequence, userId)) {
if(isParticipated(key, userId)) {
throw new FcfsEventException(ErrorCode.ALREADY_PARTICIPATED);
}

// 잘못된 이벤트 참여 시간
String startTime = stringRedisTemplate.opsForValue().get(FcfsUtil.startTimeFormatting(eventSequence.toString()));
String startTime = stringRedisTemplate.opsForValue().get(FcfsUtil.startTimeFormatting(key));
if(startTime == null) {
throw new FcfsEventException(ErrorCode.FCFS_EVENT_NOT_FOUND);
}
Expand All @@ -43,43 +44,44 @@ public boolean participate(Long eventSequence, String userId) {
}

// 이벤트 인원 마감 여부 확인
if (isEventFull(eventSequence)) {
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
if (isEventFull(key)) {
log.info("Event Finished: {},", stringRedisTemplate.opsForZSet().zCard(FcfsUtil.winnerFormatting(key)));
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
return false;
}

stringRedisTemplate.opsForZSet().add(FcfsUtil.winnerFormatting(eventSequence.toString()), userId, System.currentTimeMillis());
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(eventSequence.toString()), userId);
log.info("{} 선착순 이벤트 참여 성공", userId);
stringRedisTemplate.opsForZSet().add(FcfsUtil.winnerFormatting(key), userId, System.currentTimeMillis());
stringRedisTemplate.opsForSet().add(FcfsUtil.participantFormatting(key), userId);
log.info("Participating Success: {}, User ID: {}", eventSequence, userId);
return true;
}

private boolean isParticipated(Long eventSequence, String userId) {
return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(FcfsUtil.participantFormatting(eventSequence.toString()), userId));
private boolean isParticipated(String key, String userId) {
return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(FcfsUtil.participantFormatting(key), userId));
}

// 이미 종료된 이벤트인지 확인
private boolean isEventEnded(Long eventSequence) {
return Boolean.TRUE.equals(booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(eventSequence.toString())));
private boolean isEventEnded(String key) {
return Boolean.TRUE.equals(booleanRedisTemplate.opsForValue().get(FcfsUtil.endFlagFormatting(key)));
}

// 인원수 마감 여부를 확인하며, synchronized를 통해 동시성 제어
private synchronized boolean isEventFull(Long eventSequence) {
if(isEventEnded(eventSequence)){
private synchronized boolean isEventFull(String key) {
if(isEventEnded(key)){
return true;
}

Long nowCount = stringRedisTemplate.opsForZSet().size(FcfsUtil.winnerFormatting(eventSequence.toString()));
Long nowCount = stringRedisTemplate.opsForZSet().size(FcfsUtil.winnerFormatting(key));
if(nowCount == null){
throw new FcfsEventException(ErrorCode.FCFS_EVENT_NOT_FOUND);
}
Integer maxNumber = numberRedisTemplate.opsForValue().get(FcfsUtil.keyFormatting(eventSequence.toString()));
Integer maxNumber = numberRedisTemplate.opsForValue().get(FcfsUtil.keyFormatting(key));
if (maxNumber == null) {
throw new FcfsEventException(ErrorCode.FCFS_EVENT_NOT_FOUND);
}

if(nowCount >= maxNumber){
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(eventSequence.toString()), true);
booleanRedisTemplate.opsForValue().set(FcfsUtil.endFlagFormatting(key), true);
return true;
}
return false;
Expand Down
20 changes: 10 additions & 10 deletions src/main/java/hyundai/softeer/orange/event/fcfs/util/FcfsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,28 @@
public class FcfsUtil {

// 선착순 이벤트 tag
public static String keyFormatting(String fcfsId) {
return fcfsId + ":fcfs";
public static String keyFormatting(String key) {
return key + ":fcfs";
}

// 선착순 이벤트 시작 시각 tag
public static String startTimeFormatting(String fcfsId) {
return fcfsId + "_start";
public static String startTimeFormatting(String key) {
return key + "_start";
}

// 선착순 이벤트 마감 여부 tag
public static String endFlagFormatting(String fcfsId) {
return fcfsId + "_end";
public static String endFlagFormatting(String key) {
return key + "_end";
}

// 선착순 이벤트 당첨자 tag
public static String winnerFormatting(String fcfsId) {
return fcfsId + "_winner";
public static String winnerFormatting(String key) {
return key + "_winner";
}

// 선착순 이벤트 참여자 tag
public static String participantFormatting(String fcfsId) {
return fcfsId + "_participant";
public static String participantFormatting(String key) {
return key + "_participant";
}

// 선착순 이벤트 정답 tag
Expand Down

0 comments on commit 0335042

Please sign in to comment.