
- 대용량 데이터 처리 실습을 위해, 테스트 코드로 유저 데이터를 100만 건 생성해주세요.
- 데이터 생성 시 닉네임은 랜덤으로 지정해주세요.
- 가급적 동일한 닉네임이 들어가지 않도록 방법을 생각해보세요.
- 닉네임을 조건으로 유저 목록을 검색하는 API를 만들어주세요.
- 닉네임은 정확히 일치해야 검색이 가능해요.
- 여러가지 아이디어로 유저 검색 속도를 줄여주세요.
- 조회 속도를 개선할 수 있는 여러 방법을 고민하고, 각각의 방법들을 실행해보세요.
README.md에 각 방법별 실행 결과를 비교할 수 있도록 최초 조회 속도와 개선 과정 별 조회 속도를 확인할 수 있는 표 혹은 이미지를 첨부해주세요.
Controller
@GetMapping("/user/nickname") public ResponseEntity<User> getUserByNickname(@RequestParam String nickname) { User user = userService.getUserByNickname(nickname); return ResponseEntity.ok(user); }Repository
Optional<User> findByNickname(String nickname);Service
public User getUserByNickname(String nickname) { return userRepository.findByNickname(nickname) .orElseThrow(() -> new IllegalArgumentException("사용자가 없습니다.")); }

| 항목 | 값 |
|---|---|
| 성공률 | 63.91% (673/1053) |
| 실패률 | 36.08% (연결 거부 등 380건 실패) |
| 평균 요청 응답 시간 | 12.86초(12860ms), 최대 39.8초(39800ms) |
| 동시 사용자 (VU) | 1000명 동시에 요청 |
| 요청 수 (총) | 1053건, 초당 약 26건 처리 |
actively refused 발생: 서버가 감당 못하고 연결 자체를 끊어버림조회를 진행할때 인덱싱이 되어 있지 않으면 풀 스캔 -> nickname 인덱싱
@Table(name = "users", indexes = {
@Index(name = "idx_nickname", columnList = "nickname")
})
nickname 컬럼에 인덱스 지정주의
CREATE INDEX idx_nickname ON users(nickname);

| 항목 | 값 |
|---|---|
| 성공률 | 97.93% (29,272 / 29,888) |
| 실패률 | 2.06% (연결 거부 등 616건 실패) |
| 평균 요청 응답 시간 | 0.331초(331.98ms), 최대 0.711(711.46ms)초 |
| 동시 사용자 (VU) | 1000명 동시에 요청 |
| 요청 수 (총) | 29888건, 초당 약 2901건 처리 |
nickname 필드에 인덱스 추가 후, 성능이 비약적으로 향상됨병목 가능성: 다수의 요청이 들어 올 경우 커넥션 부족으로 대기 시간 존재
-> .yml파일 maximumPoolSize, connectionTimeout 조정
spring:
datasource:
url: jdbc:mysql://localhost:3306/${MYSQL_NAME}?useSSL=${SSL}&allowPublicKeyRetrieval=${ALLOWPUBLICKEYRETRIEVAL}
username: ${MYSQL_USERNAME}
password: ${MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 32 //동시에 사용할 수 있는 최대 커넥션 (서버 CPU의 스레드 * 2)
minimum-idle: 10 // 최소 대기 커넥션
idle-timeout: 30000 // 최대 대기 시간
max-lifetime: 1800000 // 커넥션 하나의 최대 생존시간
connection-timeout: 30000 // 커넥션 획득까지 기다릴 최대 시간

| 항목 | 값 |
|---|---|
| 성공률 | 98.75% (30,378/30,761) |
| 실패률 | 1.24% (383건) |
| 평균 요청 응답 시간 | 0.323초(323.39ms), 최대 0.616초(616.23ms) |
| 동시 사용자 (VU) | 1000명 동시에 요청 |
| 요청 수 (총) | 30761건, 초당 약 2984건 처리 |
한번 호출한 유저 정보를 Redis에 10분 간 저장하고, 10분내에 재 호출 할 경우 Redis에서 바로 응답
spring:
data:
redis:
host: localhost
port: 6379
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 10분 TTL
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
}
}
@Cacheable(value = "userByNickname", key = "#nickname", unless = "#result == null")
public User getUserByNickname(String nickname) {
return userRepository.findByNickname(nickname)
.orElseThrow(() -> new IllegalArgumentException("사용자가 없습니다."));
}

| 항목 | 값 |
|---|---|
| 성공률 | 100 (40117) |
| 실패률 | 0 (0) |
| 평균 요청 응답 시간 | 0.250초(250.80ms), 최대 0.404초(404.21ms) |
| 동시 사용자 (VU) | 1000명 동시에 요청 |
| 요청 수 (총) | 40117건, 초당 약 3919건 처리 |


