반응형

 최근에 제 토이 프로젝트를 손 보면서, 바꾼 것이 몇 가지 있습니다. 이 중에서 UserService 단에서 예외 처리하는 부분을 추가하였습니다. 해당 부분은 여기서 볼 수 있어요. 현재 제 토이 프로젝트 레포에 있는 예외 처리 코드들이 살짝 비대한 경향이 있어서 추가적인 리팩토링을 할 예정입니다. 그 부분은 나중에 언급할 기회가 있을 듯 싶습니다.

 

 이 글에서 설명할 부분은 이 문서와 일맥 상통한 내용이니 간단하게 읽어보시면 도움이 될 듯 합니다.

 


 원래 regUser는 위와 같이 구현이 되어 있었습니다. 그리고 예외가 발생하면 Service 단에서도, Controller 단에서도 처리가 되지 않는 구조였습니다.

 

 

 제 도서관 api의 db에 저장되어 있는 user_name은 위와 같습니다. 'cho'가 있으니, user_name이 'cho'인 데이터를 하나 추가해서 register를 시도해 보겠습니다.

 

 

 뭔가 명령어가 복잡해 보이지만, swagger로 테스트 했기 때문에, try-it-out만 채워주면 됩니다. 'cho'가 중복되었으니, 중복된 경우에 처리를 따로 해야 할 겁니다.

 

 

 그런데 뜬금없이 500이 떨어집니다. 500이 떨어진 이유는 무엇이였을까요?

 

 

 이미 'cho'가 있었기 때문입니다. user_name은 unique 하다는 조건이 걸려 있으니, 'cho'를 등록한다고 하면 문제가 생길 겁니다. 그런데, 그렇다고 해서 500은 문제가 있어 보이긴 합니다. 왜냐하면, 중복된 케이스에 대해서 잘 걸러냈기 때문입니다. 클라 쪽에서 이미 있는 아이디를 다시 등록하려고 했기 때문에, 4xx대 에러를 떨구는 게 좋지 않을까요?

 


 regUser를 이렇게 바꿔 보았습니다. insert를 하는 도중에 Exception이 떨어지면, -1을 돌려줍니다.

 

 

 그리고 서비스 단에서 regUser를 한 결과가 0보다 작다면, id가 중복된다는 메세지와 409 코드를 떨굽니다. 원래는 403을 떨구게 했는데 리소스가 충돌되는 것이니, 409가 나은 선택일 듯 하더라고요.

 

 

 cho가 중복된다는 메세지가 나오니 잘 처리된 것 처럼 보입니다. 그런데, Exception e 이렇게 처리하는 것은 생각보다 좋지 못합니다. 광범위한 예외이기 때문입니다. 사실, insert를 하다가 떨어지는 Exception 은 매우 많습니다. 그 중 한 예를 들어보도록 하겠습니다.

 

 

 postgresql을 stop 시켜 보겠습니다. 저는 postgresql이 돌아가고 있는 서버에서 spring boot 서버를 돌리고 있으니, 저 서비스가 stop이 되면 데이터 베이스에 당연히 접근을 하지 못할 겁니다.

 

 

 이번에 POST /reg api에 유저 이름이 'dijkstra'인 데이터를 보내 봅시다.

 

 

 그랬더니, dijkstra가 중복된다는 에러 메세지가 뜨게 됩니다. 그런데, 실제로는 유저 이름이 'dijkstra'인 케이스는 없었습니다. 논리적으로 모순된 상황이 나타나 버린 겁니다. 그러면 어떤 예외가 잡혔길래, 'dijkstra'가 중복된다는 모순된 결과가 나타났는지 볼까요?

 

 

 Failed to validate connection. 즉 Connection과 관련된 Exception이 떴을 거라는 짐작을 할 수 있습니다. 데이터 베이스 서비스가 내려가서 발생한 건데, 이것이 Exception에 속했고, 이 Exception이 catch가 되었기 때문에, 'dijkstra'가 중복된다는 이상한 결과가 나온 셈입니다.

 

 


 user_name이 중복되어서 떨궈진 Exception은 DuplicateKeyException입니다. 이것이 걸렸을 때만 잡도록 하겠습니다.

 

 

 그러면, ConnectException이 떴을 때, 409가 떨어지지 않고, Internal Server Error를 떨어트리게 됩니다. 왜냐하면, 연결 예외가 키가 중복되는 예외가 아니기 때문에, catch되지 않기 때문입니다.

 

 

 아까와는 다르게 500이 떨어집니다. 데이터베이스가 내려갔고 서버 내에서 db와 통신을 할 수 없었기 때문입니다.

반응형

댓글을 달아 주세요

  1. 구데타마

    id를 조회하고 존재한다면 409를 던지는 방법은 성능상 이슈가 있을까요?

    • 코딩강아지
      2021.10.08 23:10 신고

      성능상 이슈라.
      그거까진 깊숙하게 생각을 하진 못했네요.

      user_name이 unique가 걸려 있어서
      중복되는 경우에는 duplicate 익셉션을 떨궈서
      거기서 중복 아이디가 있음을 체크했어용.

    • 지나가던나그네
      2021.10.09 23:04

      id를 조회한 후 있는지 검증하는 것보다, unique 제약조건을 걸고 예외가 발생하는 것을 확인하는 게 더 좋습니다. Postgresql 기본값인 READ-COMMITTED 에서는 FOR UPDATE 락 걸고 조회해도 괜찮긴 한데요, Mysql 기본값인 REPEATABE-READ 에서는 FOR UPDATE 락 걸고 해도 동시에 트랜잭션이 실행되는 경우에 두 개의 Row가 생길 수 있습니다. 이 경우 unique 제약조건을 걸어주고 예외를 잡아 처리하는 게 유일하게 버그를 해결하는 방법입니다.

    • 지나가는나그네
      2021.10.09 23:06

      성능 상의 이슈는 적절한 인덱스만 걸려 있다면 수천만 row 까지는 큰 문제 없을겁니다. 하지만 그런 큰 DB에서는 어느 쿼리가 느려질지 모르기 때문에, 지속적인 성능 모니터링은 필수입니다.

    • 구데타마
      2021.10.10 13:19 신고

      답변 감사합니다! 현재 실무에서는 PostgreSQL을 쓰고 있는데 MySQL과 그런 차이가 있는지 처음 알았네요.

  2. 팽펑

    1. 리턴 코드
    database를 다룰 때, 일반적으로 DML 요청의 리턴값은 "영향을 받은 row 수"라고 알고 있습니다. 아마 DB 마다 다를 것이고 DB 커넥터/드라이버 마다도 다를 것 같습니다만, 일단 눈에 보이는 SO 링크 하나를 첨부해봅니다: https://stackoverflow.com/a/57157320/8556340

    따라서 바꾼 것이 없는 상태에서 "-1" 리턴은 적절치 않아보입니다.

    ps) C 언어였다면 -1이 적절할 수도 있겠습니다: https://www.quora.com/What-is-the-significance-or-meaning-of-using-return-1-in-C

    2. "이것은 서버 문제가 아니기 때문"이라는 표현은 적절치 않아 보입니다. 관점의 차이라고 말씀하실 수도 있겠습니다만, 실제 직접 수정하신 코드도 '서버' 내에 있는 코드인데 서버의 문제가 아니라면 이것을 뭐라고 불러야 할지 너무 모호해진다고 생각합니다. '서버 문제'라는 표현을 "서버가 제대로 핸들링하지 않아서 생긴 예상치 못한 상황"으로 구체화하는 것이 좋아보입니다. 그리고 그렇게 구체화하는 부분에 동의하신다면 이것은 서버 문제입니다.

    • 코딩강아지
      2021.10.09 09:41 신고

      현재 토이 플젝을 보면
      컨트롤러에서 예외가 발생하면
      ResponseEntity를 리턴하는 로직이 있는데
      이거도 따로 수정해야 할 듯 보이더라고요.

      +
      서버 문제가 아니라고 표현한 건
      중복 키에 대한 걸 핸들링 하지 못해서
      생긴 상황이라 한 건데요.
      따지고 보면 저거도 그냥
      모순적인 상황이 생겼다고만 해야 했던 거 같네요.

      피드백 감사합니다.

  3. 팽펑

    (사용하고 계신 티스토리 테마가, 주인장이 아니면 대댓글을 달지 못해 그냥 따로 남깁니다)

    @구데카마
    성능상의 이슈는 굉장히 많은 요소의 영향을 받습니다. 만약 id에 인덱스가 걸려있고, 대중적으로 알려진 Database이고, 데이터의 개수가 천 만 개 이하라면 단일 master db 하나로도 말씀해주신 전략을 사용하는데 큰 이슈는 없을 것으로 생각됩니다.

    • 코딩강아지
      2021.10.09 09:43 신고

      현재 데이터 수가 그리 많지는 않아서
      db 서버 하나에서 관리하고 있어요.

      디비가 매우 커지는 경우에는
      어떻게 컨트롤 할 지는 좀 더 고민해 볼게요.

      항상 감사합니다~