개발 ━━━━━/Spring(boot)

[Spring] 이전 데이터를 불러올때 stream문과 for문의 성능 차이 테스트 - 1

GukJang 2023. 11. 18. 11:21
반응형

 

Spring 프로젝트에서 Websocket 을 이용한 채팅 기능을 구현하였다.

주고 받는 채팅 메세지들은 DB 에 저장이 되고, 채팅방에 입장시 이전 메세지들이 stream 문으로 불러와진다.

 

stream 문 보단 for 문이 더 익숙한 나로선

실시간성이 중요한 채팅에서 stream 문과 for 문으로 각각 구현시 유의미한 성능 차이가 있을지 문득 궁금해졌다.

 


 

채팅 메세지들은 MySQL 과 Redis 에 저장되고 있고 Redis 에서만 불러오게끔 되어 있었는데

프로젝트를 진행하면서 이런 저런 테스트를 진행하다 보니

인메모리 데이터베이스인 Redis 에 있던 데이터들이 자꾸 날아가는 상황이 발생하였다.

 

그래서 채팅 메세지 불러오는 로직을

Redis 에 데이터가 있을 때)

1. Redis 데이터랑 MySQL 데이터가 같을 때 or Redis 데이터만 100개 이상일 때 - Redis 에서만 데이터 100개를 불러온다.

if(redisChatMessageCount == mysqlChatMessageCount || redisChatMessageCount >= 100){
	List<ChatMessage> chatMessageList = redisMessageTemplate.opsForList().range("chatMessages::" + roomId, 0, 99);
	List<ChatMessageResponseDto> chatMessageResponseDtoList = new ArrayList<>();
	for (ChatMessage chatMessage : chatMessageList) {
		chatMessageResponseDtoList.add(new ChatMessageResponseDto(chatMessage));
	}

 

2. Redis 데이터랑 MySQL 데이터랑 다를 때 - Redis 데이터 개수 확인 후, 100 - Redis 의 값만큼 MySQL 에서 불러온다.

else{
	List<ChatMessageResponseDto> chatMessageResponseDtoList = chatMessageRepository.findAllByRoomId(roomId)
		.stream()
		.limit(100 - redisChatMessageCount)
		.map(ChatMessageResponseDto::new)
		.collect(Collectors.toList());

	List<ChatMessage> chatMessageList = redisMessageTemplate.opsForList().range("chatMessages::" + roomId, 0, redisChatMessageCount);
	for (ChatMessage chatMessage : chatMessageList) {
		chatMessageResponseDtoList.add(new ChatMessageResponseDto(chatMessage));
	}
	return chatMessageResponseDtoList;
}

 

Redis 에 데이터가 없을 때)

3. Redis 에 데이터가 아예 없을 때 - MySQL 에서만 데이터 100개를 불러온다.

else{
	List<ChatMessageResponseDto> chatMessageResponseDtoList = chatMessageRepository.findAllByRoomId(roomId)
		.stream()
		.map(ChatMessageResponseDto::new)
		.toList();

	return chatMessageResponseDtoList;
}

 

이런 식으로 로직을 짜놓았는데

프로젝트 특성상 100개 이상의 메세지를 확인할 일도 없고

사용을 하다보니 이런 저런 문제들이 아직 많기도 해서

좀 더 효율적으로, 오류가 없게끔 구현을 해봐야겠다. (지금 드는 생각은 MySQL 데이터와 Redis 데이터를 실시간으로 동기화를 시킨다던가...)

 

그래도 일단 테스트는 해볼 수 있으니 진행해보았다.

 


 

테스트는 stream 문과 for 문 각각 진행을 할 것인데

메세지 개수에 따라 (1개, 10개, 100개) 총 6번의 테스트를 진행할 것이다.

 

추가로

데이터를 불러오는 로직 (위 3가지 case) 에 대한 테스트도 진행함으로써

MySQL 에서 불러오는 속도와 Redis 에서 불러오는 속도를 추가로 비교해볼 수 있을 것 같다.

 

테스트 방법은

채팅 메세지를 불러오는 로직 위에 StopWatch 클래스를 선언 및 시작해주고

dto 를 반환하기 전까지의 걸린 시간을 측정하는 식으로 진행하였다.

StopWatch stopWatch = new StopWatch();
stopWatch.start();

// ~ 이전 메세지 불러오는 로직 ~

stopWatch.stop();
System.out.println("Case 1 (stream) : "+ stopWatch.getTotalTimeMillis() + "ms");

 


 

(Redis 에 저장된 데이터로만 불러오는 환경)

1. 저장된 메세지가 1개일 때

stream문)

1 2 3 4 5 6 7 8 9 10
200 201 200 198 200 202 201 215 216 199
평균 203.2

(단위 : ms)

 

for문)

1 2 3 4 5 6 7 8 9 10
202 200 201 202 198 200 200 201 199 202
평균 200.5

(단위 : ms)

 

2. 저장된 메세지가 10개일 때

stream문)

1 2 3 4 5 6 7 8 9 10
209 210 256 219 201 203 210 218 204 212
평균 214.2

(단위 : ms)

 

for문)

1 2 3 4 5 6 7 8 9 10
200 203 200 204 202 200 200 200 200 201
평균 201

(단위 : ms)

 

3. 저장된 메세지가 100개일 때

stream문)

1 2 3 4 5 6 7 8 9 10
412 411 411 413 405 407 438 482 446 436
평균 426.1

(단위 : ms)

 

for문)

1 2 3 4 5 6 7 8 9 10
414 401 397 390 397 390 397 407 391 393
평균 397.7

(단위 : ms)

 

메세지가 1개일 때는 차이가 거의 없다시피 했지만

10개, 100개 불러오는 메세지 갯수가 늘어갈수록 for 문이 불러오는 속도가 대체로 빨랐고

stream 문에선 가끔 측정 값이 튀는 것을 확인할 수 있는데 for 문은 비교적 안정적으로 값을 뽑아내는 듯 하다.

 


 

2번째 테스트인 데이터를 불러오는 원천 DB 에 따른 속도를 확인하려 한다.

 

(불러오는 메세지 100개 기준)

1. Redis

stream 문)

1 2 3 4 5 6 7 8 9 10
412 411 411 413 405 407 438 482 446 436
평균 426.1

(단위 : ms)

 

for 문)

1 2 3 4 5 6 7 8 9 10
414 401 397 390 397 390 397 407 391 393
평균 397.7

(단위 : ms)

 

(불러오는 메세지 100개 기준)

2. MySQL

stream 문)

1 2 3 4 5 6 7 8 9 10
24 14 10 12 25 11 18 29 15 11
평균 16.9

(단위 : ms)

 

for 문)

1 2 3 4 5 6 7 8 9 10
18 10 11 10 11 25 16 11 11 13
평균 13.6

(단위 : ms)

 

(불러오는 메세지 100개 기준 / Redis 50개, MySQL 50개)

3. Redis + MySQL

stream 문)

1 2 3 4 5 6 7 8 9 10
222 212 224 221 217 211 219 218 214 211
평균 216.9

 

 

for 문)

1 2 3 4 5 6 7 8 9 10
236 221 217 217 214 217 222 223 215 217
평균 219.9

 

 

예상과는 빗나간 결과가 나왔다.

빠른 속도가 강점인 Redis 보다 MySQL 에서 불러오는 속도가 더 빠르게 측정되어 뭔가 이상해 서버를 좀 뒤적거렸는데...

 

MySQL 은 RDS 를 이용하는 중이고

Redis 는 ec2 안에 docker 에서 띄우고 있는 Redis 에서 돌아가는 중인줄 알았는데 알고보니 프로젝트 초반, 한 팀원이 설정해두었던 클라우드 기반 redis labs 에서 사용하던 Redis 를 그대로 사용 중이었다.

 

MySQL 과 Redis 의 차이를 확실하게 알려면

EC2 안에서 둘다 Docker 로 띄워진 상태여야 하는데 추후에 개인 서버에서 진행해봐야겠다...

 

 

 

그래서 어떤 이유로 이렇게 성능 차이가 나나 검색을 해보았는데

 

1. for 문은 단순 인덱스 기반으로 실행되는 반복문으로 메모리적으로 접근을 하여 stream 문 보다 빠르며

2. stream 문은 함수 호출과 람다 표현식의 오버헤드가 있지만 for 문은 없다.

 

정도 인 것 같고

 

stream 에서 parallelstream 이란 것을 이용하면 멀티 코어 프로세서의 이점을 활용하여 병렬로 처리할 수 있어 속도가 더 빠를 수도 있다고 한다.

 

아무튼 결론은

for 문의 속도가 확실히 빠른 것 같고

실시간성이 제일 중요한 채팅 기능에선 가독성이 좋은 Stream 문 보단 For 문을 쓰는 것이 더 적합해보인다.

 

DB 에 따른 차이는 눈으로 확인하진 못했지만

비싼 돈 들여서 RDS 를 쓰는 이유를 알 것 같고 stream 문과 for 문의 차이는 확인했으니 만족!

 

 

 

참고

https://jeong-pro.tistory.com/185

https://pamyferret.tistory.com/49

https://velog.io/@seungwonni/Java%EB%B0%98%EB%B3%B5%EB%AC%B8-%EC%84%B1%EB%8A%A5%EB%B9%84%EA%B5%90For%EB%AC%B8-%ED%96%A5%EC%83%81%EB%90%9CFor%EB%AC%B8-Stream-filter

 

반응형