로직 구조화 작업을 진행하면서 for문, 중첩 for문을 어떻게 다듬는게 좋을지 많은 생각을 해보았다.
물론 단순 반복문 수행 상황에서 성능적인 측면을 고려할때 for문이 낫긴 하지만(새로운 객체 생성 및 모든 반복 수행 등으로 인해), 어느 적정 데이터 혹은 조건 상에서는 stream과 for의 성능차이가 별로 없기 때문에 이 임계점을 알아보고자 stream에 대해 깊이 탐색해보았다.
탐색하는 과정에서 stream은 단순히 java 8이후 참조 변수 대상의 반복문의 성능을 높이는 수단이 아니라, 대용량 데이터 조회 시 성능을 높여줄 수 있는 하나의 방법일 수도 있다는 글이 있었기에 향후 활용도가 매우 높을 것이라 생각하여 이 글을 기록한다.
stream은 java8 이후 버전에서 지원하는 collection에 대한 함수형 프로그래밍 기능 중 하나로, 내부적인 처리를 통해 리스트를 순회하고 데이터를 알맞게 가공 혹은 필터링할 수 있도록 한다.
참고로 이중 반복문을 구현할 시 stream에 사용하는 두 리스트의 데이터 타입은 동일해야 한다.
Stream<String> stream1 = Stream.of("Apple", "Banana", "Melon");
Stream<String> stream2 = Stream.of("Kim", "Lee", "Park");
List<String> aNames = List.of("Song", "Belle", "Andrew", "Kevin", "Jia");
List<String> bNames = List.of("Song", "Andrew");
List<String> NamesLambda = aNames
.stream()
.filter(name -> bNames.stream().anyMatch(Predicate.isEqual(name)))
.collect(Collectors.toList());
많은 블로그에서 대량 데이터에서는 stream이 알맞지 않다, break나 continue를 사용할 수 없기 때문에 단일 조건 상에서 가독성있는 로직을 구현하기 위한 목적으로는 알맞다, collection 객체를 생성하기 때문에 메모리 누수 위험이 발생할 수 있다(connection 사용) ... 이러한 단점을 많이 기술하였다.
그러나 더 찾아보니, stream의 내부적인 "병렬처리"로 인해 대용량 통계 데이터 조회 시 stream이 효과적인 대안이 될 수 있다는 글도 존재하였다.
여기서의 핵심은 "List을 사용해서 모든 데이터를 함꺼번에 메모리에 적재했다면, Stream는 모든 배열을 즉시 메모리에 할당하는 것이 아니라 필요한 경우에만 데이터를 처리하게 된다"라는 점이다.
일단 이론상으로는 이해했는데, 보니까 해당 서비스를 void로 처리하거나(이때 stream의 병렬처리를 이용하는듯) mapper에서 cursor<>형태로 처리하거나 하여 성능을 개선하는 것처럼 보였다.
특히 이상적이었던것은 fetch size를 조절하여 메모리 부하를 막아도, stream을 이용하면 내부적인 병렬처리로 인해 fetch size를 낮추지 않아도 가장 조회 성능이 좋았다는 점이다.
stream - https://velog.io/@sloools/Java-Stream, https://hbase.tistory.com/171
대용량 통계 쿼리와 stream 병렬처리 - https://coor.tistory.com/46, https://flowlog.tistory.com/m/98#google_vignette