나는 10주간 무엇을 하고 느꼈는가?

10주간을 되돌아 보며

10주동안 이커머스에서 발생할 수 있는 여러가지를 학습을 하였다. 1주차로 워밍업을 하고 2~10주차에서 본격적으로 설계를 진행하였다.
사실 어떤것을 학습을 하였는지 구체적으로 기억은 잘 나지 않는다. 일단 기억이 나는데로 얘기를 해보자면

1주차

1주차때 테스트 코드를 작성하였다. 테스트 코드의 중요성은 잘 알고 있었지만 뭐랄까 막연한 두려움이 있었다. 어떻게 작성하면 될까...

@DisplayName("두개에서 한개를 제거하는 경우, 주문 상품의 갯수는 한 개를 리턴합니다.")
@Test
void returnOrderItemSizeIs1_whenRemove1Product() {
  //given
  OrderItemModel orderItem1 = OrderItemModel.builder()
     ...
  //when
  orderItems.remove(1L);
  //then
  assertThat(orderItems.size()).isEqualTo(1);
}

대략적으로 요런식으로 작성했던거 같다. given/when/then 형식으로 작성했다.
given: 데이터를 받고
when:어떨 때 실행하며,
then: 테스트 결과는??

전 회사에서 테스트 코드를 작성한적이 있었는데 그때 굉장히 힘들었던걸로 기억한다. 고객사에서 커러비지 80%를 요구하였고, 우리는 그것을 도달하기 위해 밤낮으로 테스트 코드를 작성했던걸로 기억한다. 그 당시에 코드 하나하나가 테스트 코드를 짜기 힘들었었다. 커버리지 1%올리는게 쉽지 않았었다. 지금에서 생각해보니 개발할때부터  [테스트 가능한 코드]로 개발하지 않았기 때문이었다. 지금 생각해보면 왜 테스트 가능한 코드로 작성하지 않았나 생각이 듭니다. 1주차때 테스트 코드를 어떻게 작성하면 좋을지 학습하면서 어떤 테스트가 좋은 테스트인지 생각해보는 계기가 되었습니다.

2주차

2주차는 다이어그램을 그렸었습니다. 뭐랄까 2주차는 다른 주차와 달리 중요성이 도출나보이지 않아보였습니다. 왜냐하면 다이어그램을 굳이 그리지 않아도 설계를 할 수 있고, 개발도 할 수 있기 때문이라 생각했습니다. 하지만 2주차를 겪으면서 설계가 굉장히 중요하다는것을 알게 되었고, 설계를 잘하게 되면 앞으로의 3~10주차가 편해진다는 사실을 깨닳았습니다. 그래서 저는 코드를 작성하기 전에 무조건 설계를 하는 습관이 생겼습니다.  2주차에서 제일 기억이 남는건 제가 도구로 멀메이드로 선택을 하였습니다. 멀메이드로 선택한 이유는 딱 하나입니다. 해보지 않았다는 점.. 해보지 않았기 때문에 멀메이드로 작성을 해보았습니다. 사용해보니 생각보다 어렵지는 않았습니다. 오히려 피그마나 drow.io같은곳에서 그리는 곳 보다 훨씬 쉬웠습니다.
코드로 작성해서 그런가.. 아무튼 멀메이드로 이것저것 그리다보니 다이어그램에 대한 막연한 두려움은 많이 사라진 느낌이 듭니다. 솔직히 말하면 1~10주차 과정중에서 가장 어려운 파트라고 여겨집니다.

그 이유는 아직도 정답을 모르겠습니다.
어떤것이 좋은 설계인지..다이어그램을 그리고 나중에 보면 다시 그려야 할거 같고.. 단순히 쉽다 어렵다 느낌이 아니라 가장 추상적인 파트라 이렇게 생각이 든것이 아닐까 생각이 듭니다.

3주차

3주차는 2주차때 설계한 내용을 바탕으로 구현을 하였고 3주차는 10주간의 과정에서 가장 힘이 들었던 주차였습니다.
왜냐하면 분명 어떻게 하는지 알거 같은데 단순히 구현이 많아서 힘들었던걸로 기억합니다. 10주 내내 글쓰기를 병행하면서 진행하였는데 유일하게 글쓰기를 하지 못했기 때문입니다. 어떻게 하면 코드를 클린하게 짤 수 있을까? 패키지 구조를 어떻게 가져가면 좋을까?
3주차를 학습하면서 요런 생각이 드는것 같습니다. 예를 들어 10주차에 배치를 학습하였는데 배치의 패키지 구조는 어떻게 가져가면 좋을까? 

4주차
4주차는 락에 대해 학습을 진행하였다. 낙관락을 사용할지 비관락을 사용할지 고민이 많았습니다. 처음 공부할때는 낙관락과 비관락은 단순히 정합성이나 성공률에 따른 선택이라 생각했었습니다. 하지만 이는 락에 대한 이해도가 부족해서 그러지 않았나 생각이 듭니다. 왜냐하면 낙관락은 하나만 성공하면 된다는건데 이걸 나는 나머지는 실패하니 정합성이 깨진다고 잘못 이해했던거 같다. 어떨때 낙관락을 사용할지 비관락을 사용할지 고민할 수 있었던거 같습니다. 

지금 생각해보면 낙관락은 진짜 전체중에서 하나만 성공하면 되겠지?라고 생각이 되어진다고 생각합니다. 여기에는 경합이 최소한으로 적은 곳에서 유리할것이라 생각이 듭니다. 반면에 비관락은 전체에 걸쳐서 그 곳을 사용하고 있으면 사용하지 못하게 막습니다. 엄밀히 말하면 쓰기락으로 쓰기를 제한하는 경우지만, 공유락은 잘 사용하지 않는걸로 알고 있어서 비관락을 쓰기락으로 설명을 하엿습니다. 아쉬운건 공유락에 대한 테스트가 미흡하여 이거에 대해 제대로 설명하지 못하는것이 조금 아쉽네요..

5주차

5주차는 10주간의 과정에 통들에서 가장 트레이드 오프가 가장 많았던 주차엿습니다. 읽기성능을 어떻게 하면 향상 시킬 수 있는지 학습을 했습니다. 인덱스, 반정규화(비정규화), 캐시를 적용하면서 어떤것이 가장 빠르고 효용적인지 살펴보았습니다. 데이터를 k6로 준비하고 API를 찔러보면서 이렇게 하면 빨라지는구나 근데 요런 문제가 있구나 이런것을 10중에 가장 뚜렸하게 배웠던거 같습니다.

인덱스: 테이블에 인덱스를 설정하게 되면 우선순위를 지정할 수 있게 도와줍니다. 하지만 인덱스는 만능이 아니기때문에 부득이하게 조회할 데이터가 많거나 잘 못 인덱스를 거는 경우 읽기 성능이 향상이 되지 않는것을 학습하였습니다. 하지만 반정규화와 캐시와 비교하면 가장 리스크가 없다고 생각합니다.

반 정규화: 정규화를 왜 하는건지 그것을 왜 반 정규화를 하는지 고민을 많이 해본거 같습니다. 읽기 성능을 우선시 하는지 쓰기 성능을 우선시 하는지 고민해보고 적용을 해봐야 한다. 속도 개선이 중요한들 쓰기작업이 많은 곳이라면 반 정규화를 하는것이 좋은건지 생각을 해봐야 한다고 생각해야 한다. 반 정규화 같은 경우는 테이블 구조를 바꾸는 행위이기 때문에 신중하게 결정해야 할거 같다.

캐시: 내가 테스트 했을때 캐시가 가장 속도가 빨랐습니다. 왜냐하면 메모리에서 읽는 것이 훨씬더 빠르기 때문입니다. 하지만 이 방법같은 경우는 메모리에 접근해서 데이터를 수시로 바꾸게 되면 좋지 않은걸로 알고 있습니다. 이는 쓰기 성능을 조금더 우선시가 되어야 한다는 건데 캐시 같은 경우는 자주 변하지 않는 데이터에 사용하는것이 조금더 효율적으로 알고 있습니다.

이렇게 3가지를 학습하면서 어떤것이 좋고 나쁘고가 아니라 이때 이걸 사용해야겠구나 저걸 사용했구나라는것을 10주차중에서 가장 많이 배웠던거 같습니다.

6주차

6주차는 서킷에 대해 학습을 진행하였습니다. 서킷 브레이커는 회로 차단기로 외부 API로 데이터를 발송 하게 되면 실패할수 도 있다. 이렇게 되면 내 서비스에 문제가 발생할 수 있기때문에 이를 미리 차단하여 우리의 서비스를 지키는 행위라 생각이 되어집니다.
4년간 회사를 다니면서 서킷을 사용해본적은 없었던거 같습니다. 대용량 트래픽 환경에서 데이터가 많이 들어가게 되면 어떻게 처리 할 수 있을지 고민할 수 있었습니다.

7주차~8주차

7주차는 이벤트에 대해 학습을 하였고
8주차는 카프카에 대한 학습을 진행하엿습니다.(그렇다고 전부는 하지 않음)

이벤트파트에서 중요한점은 어떤 기준으로 이벤트, 커멘드로 선택할 수 있는지 생각을 할 수 있어야 한다고 생각합니다.
이럴때 커멘드를 사용하고 저럴때는 이벤트를 사용하는구나를 느꼈었습니다.

8주차는 구현 보다는 카프카가 어떤지에 대해 학습을 했던것이 가장 컸던거 같습니다. 

9주차

9주차는 레디스 Zset에 대해 학습을 진행하엿습니다. 왜 랭킹 API를 만들때 레디스를 사용하면 좋을지에 대해 학습을 하였고 카프카 파트에서 데이터를 배치 이벤트로 동작을 했을때 어떻게 할 수 있는지 알아봤습니다.

stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
  StringRedisConnection redisConnection = (StringRedisConnection) connection;

  for(int i = 0; i < entries.size(); i += batchSize) {
    int end = Math.min(i + batchSize, entries.size());
    List<Map.Entry<Long, Long>> batch = entries.subList(i, end);
    Set<Tuple> tupleSet = new HashSet<>();

    for (Map.Entry<Long, Long> entry : batch) {
      Long productId = entry.getKey();
      Long sum = entry.getValue();
      double score = sum * weight;
      tupleSet.add(new DefaultTuple(productId.toString().getBytes(), score));
    }
    // ZADD NX INCR 과 유사하게 동작시킬 수 있음
    redisConnection.zAdd(newKey.getBytes(), tupleSet);
  }
  redisConnection.expire(newKey, Duration.ofDays(2).getSeconds());
  return null;
});

추후에 레디스 파이프라인을 사용해서 적용시켜봤고, RDB, ZADD, 파이프 라인에 대해 비교하면서 어떤 점이 좋은지 체크를 해봤습니다. 

10주차 

10주차는 배치에 대해 학습을 진행하였습니다. 뭐랄까 막연하게만 느꼈었는데 막상해보니 별거 아니었습니다.
리더, 프로세서, 롸이터를 컴포넌트로 분리해보며 데이터를 어떻게 넘어가는지에 대해 살펴봤습니다.
그 전부터 스프링 배치를 알고 있었는데 그때는 왜 어려웠는지 잘 모르겠습니다. 10주차 중에서 가장 변경된 파일 수가 제일 적었습니다.
이 파트에도 할말이 많긴한데 나름 재미있었던거 같습니다.

마무리

10주간의 여정을 마치고 나는 얼마나 성장했는지는 잘 모르겟다. 초반에는 글 컨셉도 바꿔가면서 열정적으로 임했지만 시간이 지나니 그런 마음보다는 일단 하자는 마음으로 변화한거 같습니다. 얼마나 앞으로 얼마나 잘할지는 모르겠지만, 10주간 학습한 위 과정들이 헛되지 않았으면 좋겠습니다. 나름대로 줄세우기를 해보면 가장 중요한 파트는 1주차. 알고보면 중요한 파트는 2주차. 트레이드 오프가 가장 많았던 파트는 5주차. 뭔가 효용성이 있을 거 같은 파트는 6주차. 가장 어려웠던건 8주차. 가장 힘들었던 3주차. 새로운 경험인 7,8,9,10주차..
요런 느낌인거 같습니다. 글을 쓰다보니 각 주차마다 분량이 차이가 나는거 같은데.. 모르겠다. 천천히 생각해보면서 글을 다듬어야 하는데 바로 바로 쓰고 있어서 그러지 않나 싶다. 

글 잘쓰고 싶어서 이것저것 시도도 많이 해봤던거 같다.
작업 도중에 글도 써봤고 작업이 완료된 이후에도 글을 써보기 도 하면서 나름대로 글에 대해 변화를 많이 준거 같다는 생각이 듭니다.
이게 얼마나 내 자산이 될지는 모르겠지만 나는 최선을 다했고 떳떳하다 설령 AI를 사용했다 한들 전반적인 코드 구현에서는 AI를 사용하지는 않았다. 최선을 다했으니 그랬으니 되지 않았나 싶다.. ㅎㅎ;

나도 글 잘쓰고 싶다.~~~ 내 글이 다른 사람에게도 잘 전달이 되었으면 좋겠다.

이제 시작이다. Let's go~!!! 

맞다 공개로 바꿔야 하는데 깜빡헀다.ㅎㅎ

댓글

Designed by JB FACTORY