핵데이 후기..같은 학습내용 정리

이번 핵데이는 혼자 하는 공부로는 배울수 없었던 많은 것들에 대해 배울 수 있는 소중한 기회가 되었다. 잊지않기 위해서 빠르게 기록한다. '기억보다 기록을' - 맞는 말씀인 것 같다.


1. 테스트코드가 프로덕션에서 완벽한 실행을 보장해 주지는 않는다. 하지만 다른 많은 것들을 보장해 주더라.

image-20190518195215422

병아리가 나름 자신이 있었던 해커톤 프로젝트의 테스트 코드들과 커버리지


image-20190518195642233


해커톤 당시, Prod 서버에서 발생한 duplicate Key 문제, 이 문제로 인해 이미지 수집 크롤러에서 보내는 요청들이 계속 500에러를 받고 있었다. import sql로 주입된 데이터들과 새로 주입하는 데이터 간에 id 충돌 문제가 생기고 있었다.


깜짝 놀라 다시 로컬에서 테스트 코드를 돌려보고, 로컬에서 api를 실행해서 크롤러의 target 위치를 로컬로 변경해서 테스트 했다.

  • 너무 잘돼!!!


그순간 잠시 멘붕이 왔던것 같다. 로컬과 prod환경이 매우 유사하다. 특히 사용하는 db에 관해선 같은 도커 이미지를 사용하여, 같은 환경으로 조성했기 때문에 prod서버에서만 이런 에러가 나는걸 이해 할 수 없었다.

그리고 내 앞과 옆엔 prod 서버의 api를 사용해서 개발을 진행하고 있는 동료가 있었다. ‘일단 prod서버를 내리고 천천히 살펴보자’라는 생각이 말도 안되는 상황이었다. 처음으로 겪어보는 압박감이었다. 실제 사용중인 유저가 있는 서비스에서 에러가 난다. 정말 멘탈이.. 무너지고 식은땀이 났었다.

멘토님이 같이 계셨던게 정말 너무 다행이었다. 상황을 말씀드리고 도움을 요청드렸고 import.sql 파일로 초기화된 데이터들을 삭제함으로써 한숨을 돌릴 시간을 얻을 수 있었다.


왜 그랬을까?


image-20190518201240230


local과 test와 prod의 환경이 달랐기 때문이었다.

  • test가 깨지지 않은 이유 : test scope의 db가 h2였다.
  • local에서 어플리케이션이 정상동작한 이유 : profile을 local로 설정했다 -> db를 h2를 사용했다.
  • prod에서 에러가 난 이유 : profile이 prod였다. -> db를 postgis를 사용했다.

즉 “로컬에서 도커로 똑같은 db를 사용하는데 왜 prod에서만 갑자기 에러가 나는거야!!!” 하는 생각이 틀린 생각이었다. h2에는 없고, postgis에서는 발생하는 문제로, import.sql로 입력한 정보는 @GenerationProperty가 정상동작 하지 않는다.


결국 test에서 prod 환경과 같은 db로 테스트 했을경우 캐치할 수 있는 에러였다.

이제는 테스트가 통과한다고, 로컬에서 정상동작한다고 “배포해도 된다!” 라는 확신은 못할 것 같다. 운영환경과 같은 환경의 develop서버가 나같은 병아리 개발자를 위해 존재하는가 보다.


안녕 import.sql… 위의 에러는 이미 몇번 마주한 기억이 있다. 지금까지 테스트 앞에 반복적으로 data생성 코드를 작성해 주는게 싫어서 import.sql의 데이터 기반으로 테스트를 했었다. 이제는 id 충돌에러가 왜생기는지 알기 전까지 import.sql를 기피하지 않을까.


추가로 BigDecimal을 GPS위치 저장을 위한 데이터 타입으로 삼았었다. 이게 큰 문제였다. (Postgis를 사용했지만, 정작 이용한 데이터 타입은… 크흠) 처음에 GPS좌표 데이터의 소수점 자리가 많았기에, 옛날에 주어들은 double 타입의 정밀도 문제를 해결하기 위해 BigDecimal타입을 부랴부랴 사용했다.


하지만 이에 대한 테스트는?

image-20190518204315782

병아리의 조잡한 AcceptanceTest 테스트코드


아무리 통합테스트를 전체적으로 실행되는걸 크게 확인하는 용도로만 사용했다고는 하지만, 너무 러프하게 테스트를 구현하였다.

image-20190518205020216

병아리의 널널한 Repository 테스트코드


“JPA와 Hibernate는 짱이니까! 내가 만든 메서드만 테스트하면 돼!” 왜 처음 사용하는 DataType에 대해 테스트해볼 생각을 안했을까. Hibernate가 짱인거지 BigDecimal타입을 처음 사용했던 내가 짱이 아니었는데

어찌되었든, 이제부터는 RepositoryTest에서 저장되는 데이터 타입에 대한 테스트를 반드시 작성하기로 한다. 이 테스트만 생각했어도 해커톤 중간에 Entity에서 GPS 좌표의 데이터 타입을 다시 double로 바꾸고, prod환경의 데이터들을 모두 날려야 할 일은 없지 않았을까. (ddl-create의 Validate는 이미 만들어진 테이블의 칼럼에 대한 수정을 하지 않기때문에)


핵데이가 아니었다면 “실제 서비스하는 서버에서 에러가 났을때”의 큰 압박감과 두려움을 어떻게 배울 수 있었을까. 아마 입사해서 크게 한번 데이고 나셔야 이 마음가짐을 배우게 되었을지도 모른다. 이제서야 '테스트는 내가 작성한 테스트만큼만 기능을 보장한다'는 점을 몸으로 배웠다.



2. Cloud서비스를 그냥 원격 컴퓨터 대여 서비스 정도로 생각했던 병아리 개발자, 그런데 그게 아니더라

image-20190518211901822

해커톤 당시 Object Storage를 대체하기 위해 구현한 파일 저장 서버


처음 시작시에 빠르게 Object Storage와 CDN을 사용하여 이미지 파일을 제공하도록 구현하였다. React에서 실제 이미지가 띄워지고, 팀원이 클러스터링 알고리즘도 완성해감에 따라 욕심이 생겼다. 정말 많은 이미지를 가지고 우리가 만든 프로젝트를 보고 싶어졌다. 하지만 병아리이자 소시민인 나는 슬슬 free tier 비용이 걱정되었고. 걱정과 욕심을 한번에 해소하고자 Object Storage를 대체할 수 있는 파일 저장 서버를 만들고자 하였다.

병아리답게 간단할거라고 생각했다. 이미지 보내면 파일로 저장하고, 일정한 filePath로 저장해서 실제 서버의 db에 들어간 filePath랑 매칭시킨다. 그러면 이 파일서버의 host주소와 함께 http://host/{filePath} 로 이미지를 get할 수 있게 구현했다.


첫번째 문제 : 실제로 간단한 로직이었고, 테스트도 통과하고, 동작도 제대로 했다. - 트래픽을 생각하기 전까지는.

멘토님이 설명해 주신 바로는 문제가 매우 컸다.

“동시에 여러 save요청이 오면?” 문제가 생긴다.

분명히 각각 배웠던 것들인데, 이런 문제를 예상할 insight가 없는걸 보니 역시 난 병아리인가 보다.

img

출처 : https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/13_IOSystems.html


Java 명령어의 I/O접근은 운영체제(OS) 를 통한다. 그리고 OS는 IO의 동기화 문제를 Locking을 통해 해결한다. (더 자세한 공부가 필요해 보인다.) 즉, 여러 프로세스가 동시에 I/O 접근을 시도할때 이미 I/O 작업중인 프로세스가 있다면, 접근을 불허한다.

image-20190518214508671

그럼 접근이 거부된 사용자는 계속 Internal Server Error와 CannotSaveException 라는 내용만 보게된다.

Naver Object Storage나, AWS S3같은 Storage Cloud 서비스는 이러한 문제를 해결하도록 내부적으로 구현해서 제공한다.


두번째 문제 : Scale Out

한가지 문제만 있는 거였으면 다행인데, 멘토님께서 두번째 문제도 설명해 주셨다. 미리 이런 점들을 배울 수 있어서, 실무에 나가 말썽쟁이 짱구 역할을 덜 하게 된게 너무 감사했다.

운좋게도 해커톤 가기 불과 몇시간 전에 Massive service basic 라는 글을 읽어서 멘토님이 어떤 내용을 설명하시는건지 조금은 이해 할 수 있었다. (항상 좋은 글을 공유해주는 혁진에게 감사를)


현재 구현된 파일 저장 서버는 하나의 어플리케이션에 Local 저장소(fileSystem)을 사용하게 구현하였다. 그런데 이미지 요청이 많아져서 이 파일 저장 서버를 Scale Out 해야한다면?

파일 저장을 Local File System에 했기 때문에 문제가 크게 생긴다. 같은 서버에 어플리케이션을 하나더 설치하는건 의미가 없고,(Spring은 URI 매핑역할만 할뿐, 대부분의 일은 File System이 하도록 만들었기 때문에) 서버를 하나 더 생성하고 로드벨런서를 둔다고 하면 각각의 파일저장서버에 파일들이 흩어져서 저장되는 문제가 생긴다. DB에서 비슷한 문제를 해결하는 방법처럼 이 Local File System들의 상태를 확인하고 서로 저장된 파일들을 동기화 시키는 방법은….. 공부를 많이해야 할 듯 하다.


어쨋든 이런 문제들에 대한 해결을 Object Storage라는 클라우드 서비스에서 제공해준다. 파일저장소가 그냥 파일저장소가 아니었다. 이야기를 듣자마자 문제를 예상하신 멘토님과, 이런 문제에 대한 서비스를 제공하시는 Cloud Service 개발자님들에게 존경심이 절로 생긴다.



3. 데이터가 억개라면 성능은…

항상 대용량 트래픽에 대한 갈증이 있었다. 너무나도 경험해 보고 싶지만, 아직은 먼 그것. 그래도 핵데이를 하면서 관련된 이야기를 많이 배울 수 있어서 좋았다.

실무에 계신 멘토님께 이런 질문을 할 기회가 없었어서, 정말 다양하고 어리석은 질문을 많이했다.

이를테면 서버가 몇개나 있어야 서비스가 돌아가나요?, 그럼 실제로 로드벨런서 같은걸 사용하나요?,그럼 그렇게 많은 서버를 담당하는 로드벨런서도 하나로는 부족할 것 같아요!, 쿠버네티스가 불안정하단 말을 페북에서 봐서 쿠버네티스를 실제 사용하는지 처음들었어요!
다시 생각할수록 스스로 바보같은 질문들을 너무 많이했었는데, 항상 ‘트래픽’에 대한 이야기가 너무 미지의 영역이었기 때문인것 같다. ㅋㅋㅋㅋㅋ 서버를 여러개 사용한다는걸 배워서 아는데도, 실제 그런지 확인받고 싶어하고, 서버 앞에 스위치를 사용한다는걸 알면서도 실제 사용하는지 확인 받고 싶었나보다. 뭔가 돌아보니 창피하다.

어쨋든 핵데이 동안 제작한 프로젝트에 대해서 성능에 관련된 이야기를 많이 해주셨다. 이부분은 조만간 멘토님 조언대로 서비스를 만들어 보고 다시 올려야 겠다.


짧게쓰는 핵데이 후기

밥이 너무 맛있었다. 아침밥 마저도 호화식이었다. 파인애플 하나마저도 맛이 남달랐던걸 보니 고급 재료를 쓰는 것이 분명하다.

회의실도 너무 넓어서 좋았다. 끊임없이 먹고 마시고 개발하고, 아마 진성 개발자의 천국같은 곳이 아닐까.

핵데이를 다녀와서 병아리에서 진화를 할 수 있었다.

망치 맞은 병아리정도로..

식은땀도 삐질삐질 흘리고, 스스로 부족한점도 많이 느끼고, 내가 학습한 것에 대한 고마움(비즈니스 로직과, Entity를 많이 수정했는데 미리 추상화 시켜놓은 코드 덕분에 살았다. 스스로 이렇게 코드를 짠 자신에게 고마움도 느꼇다.)도 많이 느꼈다.

핵데이에서 깨달은 점을 바탕으로 다시 코드를 짜고, 학습하고 성장하면 충격받은 많큼 많이 성장하는 스스로를 확인 할 수 있겠지.