동시성을 고려한 프로그래밍

 

코드를 짜면 동시성에 대해서 늘 고민하게 된다. 여러 WAS 가 동시에 이 코드를 실행 했을 때 이 후 API 나 배치 등등의 작업이 어떻게 될 것인가? 동시에 같은 row 를 select 한 후 상태를 검사하고 다음 코드를 실행하면 어떤 상황이 되는지 고민을 많이 하게 된다.

 

예를 들어서 인증을 위한 램덤 코드를 생성한다고 가정하자. code 값이 DB에서 unique 제약 조건이 걸려있고 이 때문에 값을 insert 할 때 만든 랜덤의 코드값으로 DB 를 조회하고 이 값이 없으면 insert 를 해서 코드를 생성하는 코드를 짰다.

 

이 코드에 동시성 처리를 해주지 않으면 어떻게 될까? 고민을 했다. 만약 랜덤의 코드 값을 생성했는데 이 코드가 중복된 코드는 아니고, 서로 다른 WAS 의 두 스레드에서 동시에 같은 코드를 생성했다고 가정하자. 이를 통해 DB를 조회하는 코드에 다달았을 때 기존에 DB에서 가지고 있던 코드가 아니기 때문에 if 문 내부로 들어갈 것이고 DB에 insert 를 시도 할 것이다.

 

DB 입장에서 생각하면, unique 제약조건이 걸려있고 RDBS 특성상 일관성(Consistency)을 보장하기 때문에 데이터가 중복되는 일은 없을 것이다. 즉, 하나의 스레드에서는 insert 하는 시점에 예외를 만날 것이다.

 

이렇게 되면 한명의 사용자는 정상적으로 서비스 이용을 시도했는데 예외 상황을 만나게 되는 것이다. 이는 좋지 않은 사용자 경험을 유발할 수 있다. 이 때문에 캐시 서버인 Redis 서버를 lock 용도로 해당 데이터에 대한 lock 을 획득 했을 때 데이터 insert 를 할 수 있도록 처리했고, 속도는 조금 느려질 수 있지만 이렇게 처하는 것이 낫겠다 라고

 

테스트 코드와 설계 문서를 작성하면서

 

나를 담당해주시는 시니어 개발자 분께서는 항상 작업에 들어가기 전에 설계에 공을 들인다. 그런데, 설계라는게 롤에서 티모 버섯을 설계하는 그런 느낌이 아니다. 설치할 수 있는 버섯의 종류가 다 다르고, 이에 따른 특성이 모두 나뉜다. 지형 마다 버섯의 폭발 시간이 지연 될 수도 있고 어떤 데에서는 버섯이 아니라 포션이 되는 느낌이다.

 

롤에서 티모 버섯 설치는 직관에 의존하지만, 코드 설계는 모두 머리에서 일어나는 계산에 의존해야 한다.

 

모르는 것이 하나라도 있으면 전혀 다른 동작을 할 수 있기 때문에 작업을 하기 전에 관련한 코드가 있으면 모두 뒤져봐야 한다. 현재 상황은 어떠한가? 어떤 기술이 사용되었고 발생할 수 있는 사이드이펙트는 어떻게 되나? 동시성 이슈는 해결했는가? 이 값이 굳이 DB 필드에 저장되어야 하는가? 캐시서버를 이용하면 안되나? 수정으로 작업을 해야하는가? 아니면 새로운 api 를 만들어야 하는가? 서버에 최대 어느정도까지 부담이 될 수 있는가? 발생할 수 있는 모든 예외 상황은? 그 때 응답할 코드는? 응답 메세지는? 응답 메세지는 클라이언트에서 바로 사용할 것인가? 아니면 코드로 정제해서 보여줄 것인가? 비동기 MQ 의 pub/sub 서버나 batch 서버를 이용해서 해결해야할 문제일까? 레거시와 호환이 될 것인가? null 예외가 발생할 수 있는 경우의 수는? DB에 얼마나 많은 row 가 쌓일 수 있는지? 인덱싱을 어떤 필드로 잡아야 할지? 롱폴링으로 해결할까? 콜백으로 해결할까? 

 

솔직히 설계할 때가 제일 지루한 작업인 것 같다. 모든 경우의 수를 머리속에 그리고, 모든 코드를 찾아보고 이게 말로 작성하니 아무렇지 않아보이는데 많이 힘들고 어려운 작업인 것 같다. 그런데 선배 개발자 분께서는 내게 항상 말씀해주신다.

  • 코드 작업은 그냥 설계한 대로 타이핑 하는 작업에 불과하다.

맞는 말이다. 그리고 이것이 설계를 미리 해 놓았을 때의 장점인 것 같다. 설계를 미리 해 놓으면 코드를 짤 때 고민하는 것은 아래 몇가지 정도로 단축 된다.

  • 단어를 뭐를 쓸까?
  • 가독성을 덜 헤치고 성능이 더 잘 나 올만한 코드가 없을까?
  • 설계한 대로 작성했나?

또한 설계를 하며 모든 코드를 다시 보고 머리속에서 경우의 수를 생각하기 때문에 어디에 어떤 사소한 코드가 있는지까지 모두 알게된다는 점이다. 내가 그렇다는 것은 아니고.. 선배님의 경우에 어떤 컨트롤러의 어떤 메소드에서 데이터가 어떻게 분기되고 이 데이터의 상태가 변했을 때 어떤 api 가 영향을 받는지까지.. 정말 사소한 것 하나하나까지 모두 기억하고 계신다.

 

기존의 레거시를 돌보면서

 

코드를 개선하다 보면 많은 점을 느낀다. 레거시에 대한 것을 어떻게 돌볼 것인가 고민을 하게 된다. 특히 version 2 api 출시가 아니라, 기존의 api 를 수정하는 작업을 하게 되면 이 코드가 기존의 레거시 코드에 어떤 문제를 줄 것 인가에 대해 고민을 많이 하게 된다.

  • 이 코드가 들어갔을 때 클라이언트의 동작이 달라질 것인가?
    • 달라진다면 프론트엔드에서 별도의 작업을 해주어야 하는 규모의 수정인가?
  • 이 코드를 동작시켰을 때 기존과는 다른 방식으로 데이터를 삽입한다면 다른 api 에서 이 데이터를 사용하는데 문제가 되지는 않을 것인가?

 

이 외에 근본적인 문제에 대해서 고민하기도 한다.

  • 굳이 수정으로 작업을 해야하나? version 2 를 출시하면 안되는 문제인가?
  • 어떻게 하면 서버 자원은 덜 먹으면서, 가독성이 좋은 코드를 짤 수 있는지?
    • 만약 이 클래스를 만들어 놓으면 다른 개발자가 사용할 때 문제가 생기지는 않을까?
      • 예를 들어서 가변인자를 통해서 받은 파라미터를 String 에 parsing 해주는 클래스를 만들었다고 하자. 그러면 실수로 인자 1개를 덜 넣어줘서 문제가 생길 수도 있다. 이 때 언제 문제가 발생하는가? 런타임인가? 애플리케이션 로드 시점인가? 발생했을 때 얼마나 크리티컬한 이슈가 되는가? 이렇게 쌓인 데이터는 마이그레이션(최악인 경우)을 하던 뭘하던 여튼 해결할 수 있는 수준인가?
  • 작성한 코드 때문에 트랜젝션을 스레드가 물고 있는다면 그 시간은 얼마나 더 길어지지?

 

많은 케이스에 대해서 고민을 할 수 있게 되는 것이 너무 즐겁다. 물론 선배 개발자에게 배워가면서 작업을 하고, 그 과정을 통해서 배운 것들이지만 그래도 이런 지식들이 점점 나의 것이 되어가는 자체만으로 행복하다.

 

사랑합니데이 선배님