top bar

글 목록

2020년 2월 6일 목요일

[JAVA] SynchronizedList vs CopyOnWriteArrayList

1. 배경


 Spring Bean의 기본 컨셉은 'Singleton' 이고, 이는 많은 사용자가 트래픽을 유발시키는 엔터프라이즈 환경에서 무분별한 객체 생성을 막아 메모리 관련 성능 이슈에 효율적이기 위함이다. 이러한 특징으로 인해 Spring Bean을 설계할 때는 반드시 '무상태'를 고려해야 한다.

하지만 최근 업무에서 특이사항이 생겼다. 아래와 같은 코드를 작성하게 된 것이다.

@Component
public class MetricSubmiter {

 private GaugeService gaugeService;

 public MetricSubmiter(GaugeService gaugeService) {
  this.gaugeService = gaugeService;
 }

 private List<SomeMetric> accumulatedMetricList = ??;

 public void accumulateMetric(SomeMetric someMetric) {
  accumulatedMetricList.add(someMetric);
 }

 @Scheduled(fixedDelay = 5_000)
 public void submitMetrics() {

  SomeMetric someMetric = getWorstMetric(); // from accumulatedMetricList

  if (Objects.isNull(someMetric)) {
   return;
  }

  gaugeService.submit("metric1", someMetric.getMetric1());
  gaugeService.submit("metric2", someMetric.getMetric2());
  gaugeService.submit("metric3", someMetric.getMetric3());
 }

 .
 .
 .
 .
}

 간단히 설명하면 이렇다. 특정 API에서 발생하는 metric 정보(예를 들어 응답시간과 같은)를 누적하고, 5초마다 스케줄러가 동작하여 누적된 리스트로부터 99 percentile의 metric객체를 가져와 GaugeService를 이용해 spring actuator의 'metrics'로 노출시킨다.

 'MetricSubmiter'는 Singleton이지만 'accumulatedMetricList'라는 상태를 가지고 있다. 따라서 API 요청을 처리하는 스레드들, 그리고 5초마다 동작하는 스케줄러가 모두 이 'accumulatedMetricList'에 접근하기 때문에, 해당 리스트를 동기화된 리스트로 생성해야 한다. 이런 상황에서는 어떤 Collection을 선택해야 할까?

2. 두가지 방법


동기화된 ArrayList를 생성하는 방법은 아래 두가지가 있다.

  • Collections.synchronizedList() // since 1.2
  • new CopyOnWriteArrayList (이하 COWAL) // since 1.5

 주석으로 언급했지만, 'synchronizedList'는 자바 1.2 버전 부터 존재한 것으로 보인다. 하지만 또다시 비슷한 컨셉의 COWAL이 등장한 것은 무엇 때문일까? 초창기 멀티 스레드 환경에서, SynchronizedList는 몇가지 한계를 가지고 있었다. 그 중 하나를 예를 들면, ’SyncronizedList’의 모든 읽기와 쓰기 동작은 이름에 걸맞게 ’synchronized’ 키워드로 동기화 되어 있는데, 이는 하나의 스레드만이 해당 리스트에 대해 읽기나 쓰기를 할 수 있다는 것이다. 어찌보면 매우 융통성이 없는 설계 라고 볼 수 있다.


그러므로, Java5 출시와 더불어 좀 더 유연하게 동기화된 리스트인 COWL가 등장하게 되었다.

3. 차이점


SynchronizedList와 뒤늦게 등장한 CopyOnWriteList의 주요 차이점을 정리해보자.

1) Locking of threads

 SynchronizedList는 읽기와 쓰기 동작시 'synchronized' 키워드를 사용하기 때문에 리스트 자체에 lock이 걸린다. 반면 COWL는 그 이름에서 알 수 있듯이 모든 쓰기 동작(add, set, remove, etc)시, 원본배열에 있는 요소를 복사(Copy)하여 새로운 임시배열을 만들고 이 임시배열에 쓰기 동작을 수행 후 원본배열을 갱신한다. 이 덕분에 읽기(get, etc) 동작은 lock에서 자유로울 수 있게 되고, 이는 읽기 Performance에서 COWL가 SynchronizedList보다 우월한 강력한 이유다.

2) Wirte Operations

 COWL은 쓰기 동작 수행시 명시적 락(ReentrantLock)을 사용한다. 결국 두 종류의 Collection 모두 이 동작에서 lock이 걸리는 것이다. 하지만 COWL는 SynchronizedList보다 쓰기 동작이 느리다. 이미 이야기 했지만 COWL은 비용이 상대적으로 높은 배열 복사 작업을 하기 때문이다. 아래를 보자.



더 얘기 할 것도 없다. 단순 동기화만 적용된 SynchronizedList보다 훨씬 더 많은 Overhead가 발생하는 건 당연한 이야기...

3) Behavior during Modification

일반적인 ArrayList는 'fail-fast iterator'이다. 아래의 코드를 보자.



위 코드와 같이, 반복자 동작(next메서드 호출) 도중 상태가 변경 되는 경우 'ConcurrentModificationException'을 발생 시킨다.

하지만 COWL은 아래 처럼 'fail-safe iterator'이다.



COWL가 가지고 있는 'COWIterator'의 next메서드는 상대적으로 심플한 모습이다. 딱 보면 알겠지만, 이것이 상태 변경 상황에서 Exception을 발생 시키지 않고 다시 말해 'fail-safe'한 이유는 복사한 임시배열의 참조를 가지고 있는 'snapshot'을 대상으로 반복을 수행하기 때문이다.

4) Iterating within block

3번에서 이야기한 맥락을 고려해 보면, SynchronizedList는 동기화 블록 안에서 반복이 이루어진다. 하지만 COWL는 snapshot을 대상으로 명시적 락 '밖'에서 안전하게 반복을 수행한다. 이것은 당연히 반복 성능에도 영향을 미칠 것이다.

4. Wrap up


 정리해 보자. SynchronizedList는 단순히 'synchronized' 키워드를 사용한 동기화된 List이다. 따라서 모든 읽기, 쓰기 동작에 lock이 작용된다. 반복 작업 또한 동기화 블록 안에서 수행 된다. 때문에 읽기보다 쓰기 동작이 많고, 크기가 큰 리스트의 경우에 적합할 것이다.

 반면에 COWL는 특유의 동작 방식으로 인해 반복작업(iterating)과 같은 읽기 동작의 Performance가 좋다. 다만 또 그 특유의 동작 방식으로 인해 쓰기 동작에서 상당한 Overhead가 발생한다. 따라서 쓰기보다 읽기 동작이 많고, 크기가 작은 리스트에 적용하는 것이 바람직하다.


.   .   .


 이제 처음의 상황으로 돌아가 보자. 'accumulatedMetricList'의 쓰기 동작은 해당 API로 몰리는 '모든' Request에서 수행된다. 하지만 읽기 동작은 스케줄러에 의해 5초마다 수행된다. 읽기보다 쓰기가 훨씬 많으며, 모든 요청 처리시 SomeMetric 객체가 리스트에 추가 되므로 크기 또한 급속도로 커진다. 이렇게 명백한 이유로 인해 결국 'SynchronizedList'를 선택하게 되었다.

 'Tomcat'과 같은 서블릿 컨테이너 및 각종 프레임워크 덕분에, 직접 작성하지 않고도 알게 모르게 멀티 스레드의 혜택을 보게 된다. 때문에 아무 생각 없이 멍때리고 있다가는 내가 작성하고 있는 코드가 이러한 멀티 스레드 환경에서 동작한다는 사실을 망각할 수 있다. 이것은 정합성이 어긋난 어플리케이션 상태를 유발시키는 실수로 이어질 수 있기에, 그 위험성을 내포하고 있다.

이번 글을 포스팅 하면서, '멀티 스레드 환경'에서의 동기화된 Collection 사용 뿐 아니라, Spring Singleton Bean 설계 또한 고찰 할 수 있었다. 우리는 여러 스레드들과 동거동락한다는 사실을 잊지 말아야 한다.




2020년 1월 29일 수요일

오랫만에 하는 생각 정리

 잘 나가던(내가 아니고) E-커머스 회사의 개발자 생활을 마치고, 현 직장으로 이직한지도 1년하고도 몇개월이 지났다. 이직한 곳은 대기업 통신사. 이직을 결심했을 당시 주변 사람들에게 가장 많이 들었던 말은 ‘그 회사는 내가 행복한 곳이 아니라 내 가족이 행복한 회사’라는 것이었다. 뉘앙스를 조금만 생각해봐도 알겠지만 다소 부정적인 이야기였다.

직장인으로서 행복이 뭘까. 아니, 개발자로서 행복은 뭘까. 이 부분에 대해서는 나 자신만의 확고한 신념이 있다. 물론 정답은 아니겠지만.

 나는 소프트웨어 개발을 좋아한다. 0과 1로 이루어진 컴퓨터 세상이 흥미롭고, 내가 노력과 공을 들여 작성한 나름 깔끔하고 우아한(?) 코드가 ‘제대로’ 동작할 때 희열을 느낀다. 클라우드 기반의 MSA가 대세인 요즘 (한편에는 모놀리식으로 돌아가자는 이야기도 있긴 하지만...), 관련하여 다양한 기술스택을 배우고 적용하는 것 또한 재미있는 도전 거리이다. 아마 죽을때까지(?) 공부해야 할 것 같은 컴퓨터 공/과학의 이론들도 빼놓을 수 없겠다. 마지막으로 방금 열거한 모든 지식을 동료들과 공유하고 함께 성장 한다면, 개발자로서 이보다 더 기쁘고 성취감이 있는것은 세상에 없지 않을까.

이러한 생각들에 비추어 볼때, 과연 내가 현재 근무하고 있는 곳은 개발자로서 이러한 성취감과 행복을 느끼게 해주는 곳일까? 그동안 느꼈던 몇가지를 짧게 정리하고자 한다.


“여기 직접 개발 하나요?”



 면접 말미에 ‘궁금한 점 없어요?’에 대한 내 질문이었다. 아마 많은 사람들이 이 회사의 ‘개발 조직’에 대하여 같은 의문을 품을 것이라 생각한다. 왜냐하면 이 회사의 ‘소프트웨어 개발’ 이라고 하는 것은 거의 대부분 ‘외주 개발’이라고 소문이 나 있기 때문이다. 하지만 다행히도 돌아온 답변은 '네 개발 많이 합니다’ 였다.

 이 회사에서 말하는 것은 바로 ‘내재화’. 뭐 쉽게 말하면 서비스 개발을 이 회사에 소속되어있는 개발자들이 ‘직접’ 한다는 거다. 입사를 하고 몇달을 지내다보니, 내가 들었던 답변은 확실히 맞는 말이었다. 물론 한창 개발자들을 여기저기서 뽑고 있는 상황이었기 때문에 인력이 부족할 수 밖에 없었고, 이를 (같은 계열사인) ‘Business Patner'의 인력들로 대체하기는 했다. 하지만 (내가 우려했던) 그들에 대한 일방적인 ‘관리’를 한 것이 아니라, 동료로서 함께 개발 업무를 진행했다는 것은 참 다행스러운 일이었다.

 그럼에도 불구하고, 내가 속한 조직과 또 몇몇개의 조직을 제외하면 서비스 개발의 거의 대부분이 아직까지도 ‘외주 업체’의 의존도가 높은 것이 사실이다. 태생이 IT 서비스 회사가 아닌 소위 말하는 ‘텔코(Telco)’이기 때문에, 회사의 이러한 행보가 하루아침에 변화 할 수 없다는 것을 감안하더라도 아쉽기는 하다.


열려있는 다양한 기술스택



 대기업에서는, 기존 서비스를 보다 ‘더’ 안정적으로 운영하기 위해 요즘 유행하는 도구나 기술에 대해 보수적인 태도를 취할 것이라 생각했다. 하지만 막상 내가 있는 조직의 기술 스택을  살펴보니 내 생각이 조금은 틀렸다는 것을 알게 되었다. 대부분의 주요 컴포넌트들이 'Spring Boot’로 구동되고 있었고, RabbitMQ와 Kafka, Redis 등 각종 미들웨어 뿐만 아니라 'Spring Cloud'에서 제공하는 다양한 도구들을 이용한 소규모 MSA 아키텍쳐까지 존재했다. 조금 투박하긴 하지만 Jenkins + Ansible 을 이용한 배포는 나름 편리했으며, 로깅과 모니터링을 위해 구축되어 있었던 ELK, TICK 스택의 도구들 또한 견고했다. 또 조만간 AWS나 GCP 등 Public Cloud를 도입할 것이라는 이야기가 들려오고 있다.

 뭐 여기까지는 새롭다고 하기엔 요즘 여러 회사들이 으레 사용하는 기술 스택(또는 플랫폼)이지 않느냐 라고 할수 있을지 모른다. 하지만 내가 이 회사에 입사해서 생각보다 새로운 기술적용에 열려있다고 강하게 느낀 것은 바로 ‘스칼라’ 였다. 내가 속한 팀이 담당하고 있는 대중교통 시스템에는 다양한 컴포넌트가 있는데, BIS (Bus Infomation System)도 그 중 하나이다. 이름에서 알 수 있듯이 버스의 실시간 위치정보와 도착정보를 제공해주는 녀석이다. 당연히 대중교통 서비스에서 굉장히 중요한 축을 담당하고 있는 이 컴포넌트가 전부 다 ‘스칼라’로 되어 있었다.

 스칼라가 이제 막 출시한 따끈따끈한 언어는 아니다. 하지만 여전히 자바와 스프링이 주류인 한국 IT 업계로 비추어 볼때, ‘대기업’인 이 회사에서 ‘비주류’인 언어로 중요한 컴포넌트를 구축했다는 것은 놀라운 사실이었다. 물론 이것은 어디까지나 내 기준. (스칼라 도입의 히스토리와 그 효과는 논외로 하겠다)


개발문화는?




출처:  Wikipedia

 ‘문화’라는 단어는 위에서 언급되는 것처럼 수많은 컨텍스트에서 달리 해석되는 묘한 단어이다. 한편, 개발자들 사이에서도 ‘개발 문화’라는 말은 오래 전 부터 그들만의 의미체계를 가지고 있어왔다. 그렇다면 이 ‘개발 문화’는 무엇일까?

 지극히 사견일 수 있지만, ‘개발 문화’는 ‘좋은 품질의 소프트웨어를 ‘함께' 만들고자 하는 일련의 행동’이다. 모두가 잘 아는 ‘코드리뷰’나 ‘Pair Programming’에서 부터 여러가지 효과적인 설계 방법론의 도입, DevOps와 SRE에서 말하는 수많은 원칙과 도구들, 그리고 ‘애자일’까지.. (언급하지 않은 모든 것들 포함) 이 모든 것들이 결국은 좋은 소프트웨어를 만들고 유지보수 하기 위한 좋은 개발문화 속에 있는 것들이 아니던가.

 사실 위에서 언급한 요소들을 완벽하게 실천 및 적용하는 조직은 많지 않을 것이라 생각한다. 그만큼 좋은 ‘개발 문화’는 쉽게 바꿀 수도, 만들어 낼 수도 없는 것이다. 다만 이러한 것들을 ‘추구’하는 분위기라면 이야기가 다르다. 처음엔 뜬구름 잡는 소리처럼 들려도, “좋은 품질의 소프트웨어를 만들고자 하는 일련의 행동”들을 추구하고 권장하는 문화에 속해 있는 개발자의 사고는 거기에 맞추어 지기 마련이며, 그것은 개발자 개인의 성장과 그것을 통한 조직의 성장, 서비스의 성장으로 귀결된다. 요즘 많은 메이저 IT 회사들이 ‘기술 블로그’나 여러 기술 세미나를 통해 자신들만의 개발문화, 즉 ‘분위기’를 홍보하는 것은 바로 이런 것들을 어필해서 좋은 개발자들의 이목을 끌려는 목적이 크다고 본다.

그래서 이 회사의 개발 문화는 어떠할까? 사실 ‘전사' 차원에서 ‘개발자’와 ‘기술’을 중시하는 문화를 장려하고 있진 않은것 같다. 그래서 이 회사의 개발문화에 대해서는 딱히 할말이 없다. 하지만.. (아래 계속...)


좋아질 것 같다.



 위에서도 언급 했지만 내가 속한 개발 조직은, (내가 알기로) 대략 17년도 말 즈음에 조직 재정비를 시작하여 18년도, 그리고 작년 19년도에 굉장히 많은 개발자를 채용했다. 특히나 N사, K사, C사, W사 등 이름만 대면 알만한 IT 회사로부터 좋은 개발자 분들이 많이 오셨다. 물론 유명 IT 회사 출신이라고 무조건 좋은 개발자라고는 할 수는 없겠으나, 그래도 기술을 중시하는 기업문화 또는 개발문화를 경험한 분들이 많다는 것은 긍정적인 변화의 가능성을 높일 수 있는 것이라 생각한다.

 작년 말 조직 개편으로 인한 어수선한 분위기 속에 반가운 메일을 받았다. 조직 내 어느 한 팀에서만 비정기적으로 시행 하던 ‘Tech Meetup’을 이번에 조직 전체로 확장하게 되어서 세션 발표자와 참석자를 모집한다는 내용이었다. ‘기술 공유’ 또한 아주 좋은 개발 문화의 한 요소라고 생각하기에, 매우 환영 할 만한 일이었다.  게다가 더 큰 범위의 기술조직 차원에서 ‘Tech Culture’를 개선하려는 움직임도 보인다. 이러한 일련의 상황들로 비추어 볼때, '개발 문화'는 계속 좋아 질 수 있지 않을까?


정리


그래서, 글머리에 썼던 '이 회사는 개발자가 다니기 행복한 회사인가?'라는 자문에 대한 자답은, '잘 모르겠다'이다(...). 조금은 허탈한 결론일 수 있겠지만, 그만큼 '행복'이란 것은 어떤 특정한 환경이나 상황으로 쉽게 정의할 수 없다는 것이 내 생각이다.

 결론적으로, 주어진 환경에서 내가 할 수 있는 최선을 다해 일하고, 동시에 나만의 행복을 찾아가는 것이 필요하다는 생각이 든다. 물론 필요하면 어떤 방식으로든 환경을 바꿀 수 도 있다.

 몇년전(...)까지만 해도 나름 열심히 블로그 포스팅을 했었다. 뭐 그때도 내 블로그를 찾아오는 사람들은 많지 않았지만, 누가 보지 않더라도 글이 하나 둘씩 차곡차곡 쌓여가고 내가 공부한 것이 내 것이 되는 것을 느낄때 참 즐겁고 뿌듯했던 기억이 있다. 배우고, 공유하고, 적용하는 것. 그것이 내가 추구했던, 지금도 추구하는 행복임을 다시금 떠올리며 글을 마친다.