웹을 사용할 때, 응답속도는 매우 중요하다.
하지만 같은 시간이 걸리더라도, 답답할 때가 있고 덜 답답할 때가 있다.
위처럼 돌아가면서 뭔가를 진행중임을 알려주는 것들을 스피너라 한다.
일반적으로 걸리는 시간은 같지만, 뭔가를 하고 있다는 인상을 줌으로서 이런 요소들은 사용자들에게 더 나은 경험을 하게 만들어주는 역활을 하게 된다.
일반적으로 이런 요소는 프론트엔드 개발자에겐 매우 중요한 요소이다.
하지만 백엔드에서는 이런 부분은 비교적 덜 중요한 요소이기에 무시되기 쉽다.
하지만, 정말 그럴까?
컨텐츠를 다운로드 하는 속도는 프로그래머가 컨트롤 할 수 없는 영역이다.
만약 1초에 12Mb씩 다운로드 하는 것이 최대인 회선을 쓰는 사람이라면 100Mb를 다운로드 받는데 최소 8초 이상이 걸린다.
하지만 한 번 생각해보자.
0초부터 10초동안 초당 10Mb씩 다운로드 하는 프로그램
0초부터 2초까지는 아무런 반응이 없다가
2초부터 10초까지 초당 12Mb씩 다운로드 받는 프로그램
두 프로그램 중 사용자가 느끼기에 더 빠르다고 느끼는 프로그램은 1번이 될 가능성이 더 높다.
이때 백엔드 개발자는 응답이 시작되길 기다리는 시간인 응답 대기 시간을 줄일 수 있도록 최선을 다 해야 한다.
위 코드는 데이터베이스로부터 JPA를 활용해 컨텐츠를 다운로드 받는 예시이다.
위의 코드는 단 하나의 차이만 있는데 이는 리턴 타입이 List이냐 Stream 이냐이다.
하지만 간단해 보이는 코드와 다르게, 위의 두 요소는 응답 대기에 걸리는 시간이 크게 다르다.
List와 Stream은 eager loading, lazy loading 이라는 큰 차이가 있는데, 이 요소가 백엔드의 응답 대기 시간에도 영향을 주게 되는 것이다.
따라서 실제 동작에는 아래와 같은 시간이 걸리게 된다.
List를 리턴 | Stream을 리턴 |
---|---|
위의 그림에서 중요한 요소는 초록색의 Waiting for server response
인데
이 시간이 각각 9.91ms
, 7.43ms
로 33%
정도의 대기시간에서 차이가 난다.
물론 위의 예시는 고작 1000건이라 둘다 비슷해 보이지만 List를 사용하는 방식은 데이터가 커질수록 훨씬 더 빠르게 대기시간이 증가하기에 이 사소해 보이는 차이는 데이터가 커질수록 더 큰 의미를 가진다.
아래 예시는 같은 조건에서 데이터의 크기만 10만건으로 늘려본 예시이다.
List를 리턴 | Stream을 리턴 |
---|---|
위의 예시를 통해 이 시간이 각각 300.92ms
, 70.46ms
로 327%
정도의 차이가 나게 됨을 알 수 있다.
물론 최종적으로 걸리는 시간은 같지만 서버가 마치 죽은 것 처럼 보이는 시간인 서버 응답 대기 시간을 줄여줌으로서 사용자의 경험에 차이를 줄 수 있다.
위 예시는 쿼리가 너무 간단해서 차이가 비교적 적게 드러나는 예시이다.
하지만 만약 데이터베이스에 join이 많다면? 쿼리가 복잡해진다면?
그렇다면 이로 인해 느려지는 시간은 List 방식에서는 모두 초록색의 서버 응답 대기 시간으로 모두 포함되게 되고
Stream 방식에서는 파란 부분에 골고루 해당 시간이 분배되게 된다.
그리고 사실 위에서는 사용자 경험을 중심으로 이야기했지만, 사실 Stream 방식의 가장 큰 장점은 바로 메모리 절약에 있다.
List는 모두 메모리에 올려야 하기 때문에 메모리 사용량이 높지만, Stream은 lazy loading 방식이라 메모리를 크게 절약할 수 있다는 이점이 있기 때문이다.
다만 이는 메모리 프로파일러가 있어야 보여 줄 수 있어서 이 부분은 귀찮은 관계로 패스~
Spring Boot JPA에는 Page
라는 페이지네이션 기능을 기본으로 구현해주는 기능이 존재한다.
이때 해당 기능은 Page
라는 클래스 속에 Stream
형태의 데이터를 전달해 줘서 Lazy Loading이라 오해하기 쉬운데, 이는 사실이 아니다.
위 사진은 JPA의 Page
를 사용한 예시인데, 리턴 타입은 Stream이지만, 실제 스트림을 사용하면 대기시간이 70ms
정도여야 하나 List
예시와 유사하게 300ms
이상이 걸리는 것을 볼 수 있다.
이는 실제 메모리상에 데이터를 적재하고 있을 것이라는(eager loading) 강한 의심을 하게 만드는 요소이며, 응답 시간을 줄이기 위해서는 Page
를 사용하면 안된다는 증거이다.
페이지네이션은 일반적으로 비교적 작은 단위로 데이터를 잘라서 클라이언트로 가져옴을 목표로 한다.
그런데 작은 데이터는 Stream
을 도입하는 장점이 적어서 그냥 List
형태로 반환하는 것이 더 효과적일 수도 있다.
하지만 당신이 페이지네이션을 사용하지 않는다거나, 페이지 크기가 매우 큰 숫자가 올 수 있다면 위와 같이 Stream
을 써 보는 것이 어떨까?