Skip to main content Link Menu Expand (external link) Document Search Copy Copied

created at 2024-01-27

지금까지 채팅서비스에는 정말 여러가지 기술들이 사용되었습니다. 이러한 기술들을 왜 사용했는지, 그리고 각각의 개념들에 대한 이해가 중요할 텐데요. 본 포스팅에서 자문자답 형식으로 이를 기술해보겠습니다!

Q1. Kafka 는 어떤 이유로 쓴건가요?

A1. Kafka 를 쓴 이유는 비동기와 안정성 때문입니다. API 요청의 경우에는 중간에 연결이 끊기게 되면 복구를 수동으로 해줘야하잖아요? 이걸 자동으로 해줍니다. 그리고 Kafka 의 경우 Consumer 가 직접 자기 리소스에 따라 큐에서 이벤트를 수신하니까 결국 버퍼에서 한없이 대기하다가 타임아웃으로 사라질 걱정이 없습니다. Consumer 의 재량에 따라 그때그때 받아오기 때문이죠. 즉, 시간이 오래걸리는 API 요청의 경우라도 Eventually Consistance 가 보장되기 때문에 Kafka 를 사용했습니다.

Q2. JWT는 왜 쓴건가요?

사용자 인증을 효율적으로 유지 할 수 있기 때문입니다. 대표적으로 사용자 인증을 유지할 수 있는 세션과 비교해 본다면 효율성이 도드라집니다. 먼저 세션은 인증을 서버의 메모리에 저장을 하는것이죠. 반대로 토큰은 유저의 브라우저에 저장됩니다. 그래서 자원을 아낄 수가 있죠. 그리고 세션은 수평확장된 다른 서버에도 똑같이 인증정보를 중복해서 저장해줘야합니다. 물론 중복저장을 해결하기 위해 Sticky session, session clustering 도 나왔지만, 여전히 리소스 소모 문제가 있죠. 반면 토큰은 수평확장된 다른 서버에 전달에도 추가 리소스 소모없이 즉시 sign 보고 validation 이 가능합니다. 그래서 서버를 수평확장할 때 매우 편하죠.

Q3. Redis 에 굳이 refreshToken 을 저장한 이유가 있을까요? 인메모리에 저장할 수 있지 않을까요?

A3. 물론 그렇습니다. 그런데 만약 다른 서버에서 필요하다면 해당 메모리를 참조할 수가 없습니다. 그래서 리모트 인메모리 db인 Redis 를 사용했습니다.

Q4. 자동화에 관심이 많다고 했는데 어떻게 관심이 생긴걸까요?

A4. 프로젝트를 진행할 수록 불필요한 부분에서 소모되는 시간이 많아지면서 자동화 필요성을 느꼈습니다. 예로 배포하는데만 30분이 넘어갔거든요. 그래서 배포자동화를 진행했고 지금까지 약 400번 배포했는데 총 200시간을 아낄 수 있었습니다.

Q5. 성능개선에 관심이 많다고 했는데 어떻게 관심이 생긴걸까요?

Q6. 제가 만든 서버가 수치화 되어서 값이 증가하는 부분이 매력적이였기 때문입니다.

Q7. Spring-Cloud 도 쓰셨는데, 왜 쓰셨나요?

A7. Spring-Cloud 를 사용하면 여러 yaml 설정정보들을 한번에 바꿀 수 있기 때문입니다. actuator 의 busrefresh 를 통해서 말이죠.

Q8. 프로젝트 구현을 혼자 하셨는데 왜?

A8. 프로젝트를 진행하다면서 참여자를 모집하고자 인프런과 주변에 contributor 모집을 올렸었습니다. 다만 프로젝트가 어느정도 진행되어있던 상태라 구하기 어려웠던것 같습니다. 인재풀을 넓히고자 영어로 이슈나 readme 등을 작성했었는데 아쉽게도 외국인들은 참여없이 소통만 했었습니다.

Q9. 쿠버네티스는 왜 쓴건가요?

A9. 성능개선과 자동화를 위해 사용했습니다. 그리고 비용도 관련이 있습니다. 첫 째로 성능개선에서는 HPA 의 auto-pod-scaling 을 사용해서 지표에 따라 자동으로 LB 할 수 있기 때문입니다. 그리고 프로메테우스로 모든 지표 모아서 확인이 가능하니 상당히 유용했습니다. 두 번째로 자동화는 서버 롤링 업데이트가 상당히 유용했습니다. 파드를 하나씩 새로운 버전으로 차근차근 업데이트 시켜주니까 블루-그린 배포전략을 알아서 수행할 수 있기 때문입니다. 그리고 서버 공통 secret 도 자동으로 설정해줄 수 있고, 여러가지 장점이 존재했습니다. 마지막으로 비용인데요. 기존에는 저는 docker compose 로 단일 서버 배포전략을 짰습니다. 이 때 cpu 리소스가 부족했는데, EC2 vcpu 코어 2배 늘리기 위한 비용이 4배였습니다. 수직확장 비용이 기하급수로 많이 들어갔죠. 그래서 수평확장으로 방향을 잡았고 여기에는 쿠버네티스가 유용했기 때문에 사용했습니다.

Q10. 프로젝트 진행 시 어려웠던점은 없었나요?

A10. 제 서버가 어떤 문제를 가지고 있는지 파악하는게 어려웠습니다. 이렇게 진행하는게 정말 맞는건지 스스로 알기 어려운 부분들이 많았습니다. 그래서 정말 여기저기 다 물어보고 다녔던것같아요. 인프런 강사님과 카카오에 계신 지인분, 주변 현업 친구들, stack overflow 에 질문 리스트 정리해서 다양하게 여쭤봤었습니다. 넥슨, 배민 기업 기술블로그 들도 많이 참고하구요.

Q11. 비동기 처리는 어떤식으로 진행하셨나요?

A11. Java 의 CompletableFuture 을 주로 사용했습니다. 예로 KafkaMQ 퍼블리싱, 이메일 전송과 같은 부분에서 퓨쳐객체를 사용했죠.

Q12. UnderTow 이벤트 방식 WS 를 왜 사용하셨나요?

A12. 저희 프로젝트에서는 IO 작업이 많이 필요한 부분에서 성능 개선을 목표로 했습니다. 기존에 사용하던 Tomcat 기반의 웹 서버는 IO 작업 시간이 길어질 경우 처리 속도가 느려지는 문제가 있었습니다. 이러한 문제를 해결하기 위해, 저희는 Undertow를 도입하기로 결정했습니다. Undertow는 Netty를 기반으로 하는 이벤트 주도 비동기 처리 방식을 사용합니다. 이는 서버의 IO 처리 능력을 향상시켜, 고성능과 더 나은 확장성을 제공하며, 동시에 여러 요청을 효율적으로 처리할 수 있게 해줍니다. 따라서, Undertow를 사용함으로써 IO 작업의 부하를 줄이고, 전체 시스템의 성능과 안정성을 개선할 수 있었습니다.

이 부분은 실제로 테스트 해보면서 지표를 추후에 확인해보겠습니다!

Q13. Golang 프로젝트도 진행하셨던데, Spring Java 와 Golang 의 차이점이 뭘까요?

A13. 일단 Go 와 Java 의 차이점을 말씀드리면, 스레드의 차이가 있습니다. Go 는 Goroutine 으로 M:N 스레드 매칭해서 관리하고 Java 는 네이티브 스레드로 관리하죠. 각각 크기가 2KB, 1MB 입니다. Goroutine * 500 해야지 Java 스레드가 되죠. 그래서 동시성은 Go 가 더 높습니다. 누가 동시성 퍼포먼스 측정을 했는데, Golang 이 133% 더 높은 동시성을 가진다고 하더라구요. 메모리도 JVM 처럼 많이 먹지 않아서 좋구요. 하지만 큰 규모의 어플리케이션에서는 Spring 도 매우 좋다고 합니다. GC 에서 차이점도 있습니다. Java 경우 전부 포인터로 이루어져있어서 어떤 클래스가 할당해제되면 내부 클래스 포인터 모두 찾아서 제거해줘야합니다. Golang 의 경우는 Struct 로 되어있어서 시작과 크기가 나와있기때문에 빠르게 제거할 수 있죠.

Q14. 그렇다면 Golang 의 Context Switch 비용이 높지 않을까요?

A14. 일단 Goroutine 의 CS 에 딱 3개의 register 만 스위칭 됩니다. PC 랑 스택포인터, DX 죠. 반면 Java 의 스레드는 16개를 스위칭해야하고 크기도 훨씬 크니 CS 비용이 더 높습니다.

Q15. 왜 Golang 이 아닌 Spring Java 로 프로젝트를 진행하신건가요?

A15. Spring 은 Gin 에 비해 다양한 플러그인과 라이브러리를 제공하기 때문입니다. 그래서 개발 편의성이 좋다고 생각하여 Spring Java 로 진행하게 되었습니다.

Q16. 주로 어떻게 문제점을 해결하시나요?

A16. 먼저 공식문서를 찾는 편입니다. 쿠버네티스와 Spring 의 경우 정말 잘 되어있거든요. 만약 여기서 해결이 안되면 구글링해보고 그래도 안나온다고 하면 질문들을 적어서 개발자 커뮤니티나 지인들에게 물어보는 편입니다.

Q17. Session 도 있는데 왜 토큰으로 구현하였나요?

A17. 리소스 감소와 접근성 이 두 가지 이유로 토큰을 사용했습니다. 첫 째로 리소스 감소입니다. Session 으로 사용자 인증을 관리하게 되면 동시접속자가 많을 때 서버가 유지해야하는 메모리 유지비용이 많이 소모됩니다. 토큰을 사용하면 이런 메모리를 효과적으로 줄일 수 있죠. 두 번째로 접근성입니다. 서버를 수평확장했을 때 사용자의 인증은 확장된 서버 어디에서든 적용되어야합니다. Session 은 서버 각각 다 다르기때문에 어디에는 로그인 되어있고 어디에는 로그인이 안되어 있을 수 있습니다. 토큰을 사용한다면 어디서든 자신을 증명할 수 있겠죠?

Q18. Sticky Session 으로도 로그인 세션을 구현할 수 있지 않나요?

A18. 물론 그렇습니다. Sticky Session 을 구현한다면 초기 로그인 요청된 서버로 이후 요청이 갈 수 있겠죠. 반면 이렇게 되면 로그인 파트를 모든 서비스들이 다 구현해야합니다. 그래서 로그인 서비스가 조금이라도 변한다면 모든 서비스들을 다 고쳐야겠죠? 의존성이 높아지기 때문에 이건 회사 사정에 따라 달라질 수 있을것 같습니다.

Q19. 자동화에 Git Actions 를 쓴 이유가 있을까요?

A19. Git Actions 를 사용하게 되면 직관적으로 Git 에서 merge 시 확인할 수 있어서 사용하였습니다. 이후에는 Jenkins나 Git workspace 도 사용해볼려고 생각하고있습니다.

Q20. 성능개선은 어떻게 진행하셨나요?

A20. 여러 지표를 수집하고 모니터링하기 위해서 Spring-Actuator, Prometheus 와 Grafana, micrometer 을 사용했습니다. 그리고 실제 서버의 로드, 스트레스테스트를 nGrinder 와 직접 Golang 으로 만든 gotybench 로 진행하였습니다. 성능은 캐싱, DB indexing, 쿠버네티스의 HPA 등등 여러 실험을 해보고 테스트 지표를 보면서 개선하였습니다.

Q21. 왜 이 프로젝트에서 자동화와 성능개선에 집중하셨나요?

A21. 성능 개선에 집중한 이유는 서버 지표들의 수치 변화가 재미있었기 때문이고, 자동화에 집중한 이유는 반복적인 작업에 시간을 낭비하지 않기 위해서입니다. 특히 배포 자동화에 주력했는데, 수동으로 배포하면 한 번에 30분 정도 걸리고 실수할 가능성도 있었거든요. 자동화를 통해 이 모든 과정을 단순화하고 시간을 대폭 절약할 수 있었습니다. 지금까지 340번 정도 배포했는데, 이를 모두 수동으로 했다면 170시간 정도를 소모했을 겁니다!

Q22. 자동화 할 때 발생하는 오류처리는 어떻게 하셨나요?

A22. Slack 으로 어떤 단계에서 발생한 오류인지 알림 날라오도록 설정했습니다. Job 은 Git Actions 워크플로우의 작업수행단위입니다.

Q23. 이벤트 처리방식은 왜 사용하셨을까요?

A23. 서비스 간의 결합을 줄이고 비동기 처리를 강화하기 위해 이벤트 처리 방식을 채택했습니다. 사용자 생성 프로세스를 예로 들면, 이 과정은 인증 서버, 상품 서버, 채팅 서버 등 여러 서비스에 영향을 미칩니다. 일반적인 방식에서는 이러한 서비스들 간에 직접적인 통신이 필요하죠. 그래서 의존성이 높고 동기적인 성향이 강합니다.

메시지 큐를 이용한 이벤트 처리 방식을 사용하면, 사용자 생성과 관련된 이벤트를 MQ의 특정 토픽에 게시하고 다른 서비스들이 수신만 하면 됩니다. 이렇게 되면 비동기 처리되고 디커플링 되어 개발편의성이 높아지겠죠?

Q24. 이벤트 처리방식의 장점과 단점은 무엇이 있을까요?

A24. 높은 동시성과 디커플링이 장점이구요. 단점은 이벤트 순서에 대해 제어하기 까다롭다는 점이 있습니다. 이를 해결하기 위해서 MSA Saga 아키텍처도 등장했죠. 이벤트를 중앙에서 제어하는 오케스트레이션 형태와 각각의 서비스들이 제어하는 크레오그래피 형태가 있습니다. 저는 크레오그래피 형태로 각각의 서비스들이 개별적으로 이벤트를 관리하도록 설정했습니다.

Q25. Saga 는 어떤 형태가 존재할까요?

A25. 오케스트레이션과 크레오그래피 형태가 있습니다. 오케스트레이션은 중앙서버에서 이벤트흐름을 관리함으로써 모니터링을 쉽게 할 수 있도록 하구요. 크레오그래피는 각각의 서비스들이 스스로 흐름을 관리하면서 디커플링에 강점이 있습니다.

Q26. MSA 로 하면서 어려웠던점은 무엇인가요?

A26. 아무래도 이벤트 스키마를 구현하는게 까다로웠습니다. 어떤 방식으로 롤백 할 것인지, 롤백하게 된다면 어떻게 사용자에게 이를 알릴것인지 등 부수적인 부분을 생각하는게 어려웠던것 같습니다.

Q27. Saga 각각 형태의 단점을 알고 계신가요?

A27. 오케스트레이션은 아무래도 이벤트 흐름이 몰려있다보니 중앙서버와 다른 서비스들간의 결합도가 완전 상승하구요. choreography 형태는 모든 흐름을 파악하고 모니터링하기 어렵다는 점이 있습니다.

Q28. 프로젝트를 진행하면서 뿌듯했던 점이 있나요?

A28. KT-Cloud 에서 제 프로젝트를 가져가서 사용한 부분이 가장 뿌듯했었습니다. 누군가가 제 프로그램을 사용한다는게 개발자로써 느낄 수 있는 가장 큰 행복이라고 생각하기 때문입니다.

Q29. 여러 알고리즘 중 b tree 를 쓰신 이유가 있을까요?

A29. 데이터 특성에 따라 다르겠지만, 제 경우에는 created_at 의 정렬쿼리와 범위쿼리를 효과적으로 하기 위해 b tree 를 사용했습니다. 만약 여러 칼럼이 엮인 복잡한 인덱스를 쿼리하게 된다면 GiST 을 사용하면 좋을것이고, 순차적으로 저장된 대량의 데이터를 찾을려면 BRIN 이 좋을 것입니다.

A29. btree 가 여러 범위쿼리, 정렬쿼리, 등등에 범용적으로 적용가능하기 때문입니다. 물론 데이터 특성에 따라 다르겠지만요. 제가 btree 로 최적화했던 쿼리는 Order by 로 최근날짜순으로 정렬하고 최대 1000개의 limit 을 걸어서 row 를 가져오는 쿼리입니다. 실제로 btree 뿐만 아니라 brin, GiST 도 적용해보았는데 Order By 절 내부에서는 시퀀셜하게 스캐닝 하더라구요. 지원되지 않는다는 것이죠. 범위쿼리도 따로 실험해보았는데, brin 은 정말 매우 빨랐습니다.

Q30. 해당 프로젝트를 진행하면서 비동기를 어떤 방식으로 적용하셨나요?

A30. 저는 유저 회원가입과 이메일 전송을 비동기로 구현하였습니다. Kafka 비동기 이벤트 스트림 형태로 구현되었죠.

Q31. 그냥 각각의 서버에 전송해도 되는데 왜 굳이 Kafka 를 사용하셨나요?

A31. 완전한 비동기를 위해서 입니다. 이메일 전송의 경우에는 시간이 꽤 걸립니다. 그래서 로직 내부에서 다루지 않고 외부서버로 따로 빼서 처리하게 되죠. 이 때 외부서버에 이메일 전송 요청을 보내게 되는데, 많은 요청이 오면 타임아웃이 걸려서 아예 이메일이 안보내집니다. 이걸 방지하고자 Consumer 자원에 따라 큐의 메세지를 처리하는 Kafka 를 사용해서 Eventually Consistance 하게 비동기를 구현하였습니다.

Q32. AWS NLB vs ELB 차이점이 뭔가요?

A32. NLB 와 ELB 는 로드 밸런서라는 것에서 비슷한데요. 차이점은 …

Q33. Kubernetes Deployment 와 ReplicaSet 차이점은?

Q33. 대표적으로 Deployment 는 어플리케이션을 롤아웃과 롤백을 시킬 수 있습니다. 이 설정을 통해서 무중단 배포가 가능하고 원할 땐 롤백을 버전에 따라 쉽게 시킬 수 있습니다.

Q34. 롤아웃과 롤백이 뭔가요?

A34. 롤아웃은 우리가 어떤 새로운 버전의 어플리케이션을 배포할 때, 하나씩 기존과 바꿔서 배포해주는 역할을 수행합니다. 파드 몇개씩 새로운 버전으로 차근차근 배포할 수 있어서 무중단 배포의 대표적인 기능입니다. 롤백은 뭐 어플리케이션 버전에 따라 원하는 버전으로 바꿔줄 수 있는 기능이죠.