안녕하세요, 개발자 한조각입니다.
카카오와 SK를 거치며 실제 서비스 환경에서 다양한 백엔드 시스템을 설계하고 운영해왔습니다.
그 과정에서 마주한 문제와 시행착오, 기술을 선택할 때의 고민을 지식 공유 콘텐츠에 담고 있습니다.
기술을 언제, 왜 사용하는지부터 어떤 기준으로 시스템을 설계하는지까지, 실무의 맥락과 함께 전하고 싶습니다.
제가 겪었던 고민과 경험이 여러분의 시행착오를 줄이고,
더 나은 선택을 하는 데 도움이 되기를 바랍니다.
콘텐츠를 보며 궁금했던 점이나 아쉬웠던 부분이 있다면 언제든 편하게 말씀해주세요.
여러분의 솔직한 피드백을 바탕으로 더 나은 콘텐츠를 만들어가고 싶어요.
감사합니다.
Courses
Reviews
- Complete One Full Cycle of Backend Service with Spring Boot and AWS
- Server System Design for Withstanding Traffic Spikes - Coupon Issuance Service
- Server System Design for Withstanding Traffic Spikes - Coupon Issuance Service
- Server System Design for Withstanding Traffic Spikes - Coupon Issuance Service
- Server System Design for Withstanding Traffic Spikes - Coupon Issuance Service
Posts
Q&A
아무 수정도 안하고 주어진 소스로 그대로 했는데 패스워드 에러가 남
안녕하세요, 사랑2님. 첫 번째 질문에 확인 절차를 댓글로 남겼습니다.첫 번째 질문에 남긴 답변 확인하기안내드린 명령의 실행 결과를 해당 글에 남겨주시면, 이어서 원인을 확인하고 해결 방법을 안내드리겠습니다. 정상적으로 실습을 진행하실 수 있도록 함께 확인할게요.감사합니다.
- Likes
- 1
- Comments
- 1
- Viewcount
- 42
Q&A
처음 실행 시 계속 패스워드가 틀리다고 나옴. 수정하고 해도 마찬가지임
안녕하세요, 사랑2님.제공된 소스를 그대로 실행했는데 처음부터 오류가 반복되어 불편을 드린 점 죄송합니다.정상적으로 실습을 진행하실 수 있도록 제가 방금 더 확인는데,결론은 제 로컬에서는 잘 실행이 됐습니다.그래서 사랑2님의 PC 환경을 체크해보면 좋을것같은데요.남겨주신 로그는 PostgreSQL에 접속하는 과정에서 snsuser 계정의 비밀번호 인증이 실패했다는 내용입니다. 제가 제공한 소스를 확인해보니 앱 설정과 Docker Compose의 계정 정보는 모두 snsuser / snspassword로 일치해요.다만 설정 파일의 값이 같아도, 기존 DB에 다른 비밀번호가 저장되어 있거나sns-app 애플리케이션이 PC에 설치된 다른 PostgreSQL에 접속하면 같은 오류가 발생할 수 있습니다.그런데 현재 적어주신 로그만으로는 어느 경우인지 확정하기 어렵습니다.[확인해볼사항]우선 데이터를 유지한 상태에서 실제 실행 중인 DB를 확인해야합니다.sns-app 폴더에서 아래 세 명령을 실행한 결과를 댓글로 남겨주시면 감사하겠습니다.docker compose ps -adocker compose logs --tail=100 postgresdocker compose exec -e PGPASSWORD=snspassword postgres psql -h postgres -U snsuser -d snsdb -c "SELECT current_user, current_database();"마지막 명령은 강의용 PostgreSQL에 제공된 계정 정보로 접속이 되는지 확인하는 명령입니다.오류가 나오면 그 메시지를 그대로 댓글에 남겨주시면 됩니다.사용 중인 운영체제와 앱을 IntelliJ에서 실행하셨는지, 터미널에서 실행하셨는지도 함께 알려주세요. 결과를 확인해서 필요한 해결 절차를 구체적으로 안내드리겠습니다.감사합니다.
- Likes
- 1
- Comments
- 2
- Viewcount
- 49
Q&A
11강 [실습] Docker로 MySQL1 분만에 실행하기
안녕하세요, Jang Jaehoon님!먼저 도커 데스크탑이 잘 설치되었는지 확인해주세요.docker ps 위 명령어로 도커 프로세스가 확인되는지 (도커 명령어가 잘 실행되는지) 확인해주세요.잘 된다면, 올바른 경로에서 docker compose up -d 를 확인해주세요.docker-compose.yaml 파일이 있는곳에서 실행해주셔야 합니다. 감사합니다!
- Likes
- 0
- Comments
- 2
- Viewcount
- 50
Q&A
11강 [실습] Docker로 MySQL1 분만에 실행하기
안녕하세요, Jang Jaehoon 님위 쪽의 Access denied 문제만 해결하면 될 것 같은데요.원인을 짐작할 수 있는 단서는 에러 메시지의 'coupon'@'localhost' 부분입니다. 강의에서 사용하는 mysql:8.4 이미지는 PC에서 Docker 컨테이너의 MySQL로 접속하면 접속자가 'coupon'@'172.x.x.x'처럼 IP 주소로 기록됩니다.그런데 질문 주신 에러에는 localhost로 찍혀 있으니, 애플리케이션이 Docker 컨테이너가 아니라 PC에 직접 설치된 다른 MySQL에 접속했을 가능성이 높습니다. 로컬에 설치된 MySQL이 이미 3306 포트를 쓰고 있으면 컨테이너는 정상적으로 떠 있어도 localhost:3306으로 들어온 요청을 로컬 MySQL이 받아 가게 되고, 그 MySQL에는 coupon 계정이 없으니 Access denied가 나는 것입니다.1 실행중인 컨테이너 MySQL 계정 확인)먼저 컨테이너 쪽 계정이 정상인지 확인해 보시면 좋겠습니다. docker-compose.yml이 있는 디렉터리에서 아래 명령을 실행했을 때 1이 출력된다면 컨테이너에 자체는 문제가 없음을 확인할 수 있습니다.docker compose exec mysql mysql -ucoupon -pcoupon coupon -e "select 1" 만약 위의 확인 명령에서부터 Access denied가 난다면, 볼륨에 예전 데이터가 남아 있는 경우일 수 있습니다. MySQL 이미지는 데이터 디렉터리가 비어 있을 때 딱 한 번만 MYSQL_USER, MYSQL_PASSWORD로 계정을 만들기 때문에, 이전에 다른 설정으로 컨테이너를 띄운 적이 있다면 compose 파일을 고쳐도 반영되지 않습니다. 이때는 docker compose down -v로 볼륨까지 지운 뒤 docker compose up -d mysql로 다시 띄워 주세요. 볼륨을 지우면 DB 데이터가 모두 삭제된다는 점은 참고해 주세요.또 IntelliJ Run Configuration에 SPRING_DATASOURCE_PASSWORD 같은 환경 변수를 넣어 두셨다면 그 값이 application.yaml 기본값보다 우선하니 함께 확인해 보시면 좋겠습니다.2 다른 MySQL 프로세스가 있는지 확인)다음으로 3306 포트를 어떤 프로세스가 쓰고 있는지 확인해 주세요. macOS에서는 lsof -nP -iTCP:3306 -sTCP:LISTEN, Windows에서는 netstat -ano | findstr :3306으로 확인할 수 있습니다.결과에 mysqld가 보이면(Windows는 출력된 PID를 작업 관리자에서 찾아보시면 됩니다) PC에 설치된 MySQL이 포트를 차지하고 있는 것입니다.이 경우 해결 방법은 두 가지입니다.하나는 로컬 MySQL을 중지하는 방법으로, Homebrew로 설치하셨다면 brew services stop mysql, Windows라면 services.msc에서 MySQL80 서비스를 중지하신 뒤 docker compose up -d mysql로 컨테이너를 다시 띄우고 애플리케이션을 실행하시면 됩니다.다른 하나는 로컬 MySQL은 그대로 두고 컨테이너 포트를 바꾸는 방법입니다. docker-compose.yml의 포트를 "3307:3306"으로 바꾸고, application.yaml의 url 기본값도 jdbc:mysql://localhost:3307/coupon으로 맞춰 주시면 됩니다.그래도 해결되지 않으면 위 두 확인 명령의 출력 결과를 댓글로 남겨 주세요. 같이 확인해보겠습니다.감사합니다.
- Likes
- 0
- Comments
- 2
- Viewcount
- 60
Q&A
코드 깃허브는 없을까요?
안녕하세요 hahalove77님! 수업자료는 아래 github 링크로 들어가시면 있으니 참고하시면 됩니다. 감사합니다 🙂https://github.com/apieceofcoding/springboot-twitter
- Likes
- 1
- Comments
- 2
- Viewcount
- 50
Q&A
@Modifying에 관하여
안녕하세요! Gomgomi 님좋은 질문 감사드립니다. 실무에서도 clearAutomatically, flushAutomatically 옵션을 사용할 수 있습니다, 다만 항상 설정하기보다는 해당 쿼리를 사용하는 트랜잭션의 흐름에 따라 결정합니다.말씀해주신 @Modifying을 사용하는 상황이 발생하면 영속성 컨텍스트에 있는 데이터와 차이가 발생하는 상황은 예를들어 아래와 같은 경우라고 이해했습니다.예를 들어 같은 트랜잭션 안에서 아래 코드를 실행한다고 가정하겠습니다.val coupon = couponRepository.findById(couponId) // coupon.issuedQuantity는 10 couponRepository.incrementIssuedQuantity(couponId) // @Modifying // DB의 issuedQuantity는 11 // 기존 coupon 객체의 issuedQuantity는 여전히 10 val foundAgainCoupon = couponRepository.findById(couponId) // findById()를 다시 호출해도 영속성 컨텍스트에 있는 기존 객체를 반환하므로 여전히 10위 상황은 재조회한 coupon 의 발급수량이 변경되지 않은 이전값으로 보여서 문제가 될 수 있습니다.반면, 강의의 최종 발급 코드에서는 Coupon 엔티티를 미리 조회하지 않고, UPDATE 쿼리로 발급 수량을 증가시킵니다. 이후에도 해당 Coupon 객체를 사용하지 않아요. 따라서 해당 흐름에는 오래된 값을 가진 Coupon 객체가 영속성 컨텍스트에 없으므로, 말씀해주신 두 옵션 없이 사용해도 괜찮습니다.옵션의 역할을 보면, flushAutomatically = true는 UPDATE 쿼리를 실행하기 전에, 코드에서 엔티티 값을 바꿨지만 아직 DB에는 저장되지 않은 내용부터 먼저 DB에 반영하는 옵션이고, clearAutomatically = true는 UPDATE 실행 후 영속성 컨텍스트 전체를 비우는 설정입니다.실무에서는 트랜잭션 안에서 대상 엔티티를 미리 조회했는지와 UPDATE 이후 어떻게 사용하는지를 확인한 후 위 옵션을 사용할지 말지 결정하면 됩니다. 감사합니다.
- Likes
- 1
- Comments
- 1
- Viewcount
- 54
Q&A
Issuance 테이블에 status, couponId 인덱스가 꼭 필요할까요?
안녕하세요 60jong님!말씀하신 것처럼 가짓수가 적은 status의 단독 인덱스는 효과가 제한적일 수 있습니다. 사실 현재 코드에는 status로 조회하는 쿼리가 없어 꼭 필요한 인덱스도 아니라, status 는 인덱스에서 빼도 될것같네요. 발견해주셔서 감사합니다.다만 근본적 질문으로 다시 돌아가보면, 상태가 3가지라고 해서 해당 인덱스가 반드시 필요없는 건 아닙니다. 예를 들어, 특정 상태가 전체의 1%라면 그 상태를 조회할 때는 인덱스가 도움이 될 수 있습니다. 조회 범위를 크게 줄일 수 있으니까요.또 다른 예시로는 아래 쿼리를 생각해볼 수 있습니다.WHERE status = 'ISSUED' ORDER BY created_at ASC LIMIT 20여기에 만약 (status, created_at) 복합 인덱스를 걸면 효과를 볼 수 있는데요. status = 'ISSUED' 인 행들에 대해 created_at 가 이미 정렬되어 있어서, 해당 복합인덱스로 빠르게 앞에서 20개만 읽어서 조회할 수 있습니다.그냥 지나칠 수도있는데, 이런 부분까지 잘 잡아주셔서 감사합니다.답변이 되었기를 바랍니다. 🙇♂️
- Likes
- 2
- Comments
- 1
- Viewcount
- 68
Q&A
부하 테스트 시 설정 관련 질문드립니다
안녕하세요 드루와라잇님!실무에서는 RPS를 임의로 정하기보다, 평소 최대 트래픽과 이벤트 때 예상되는 요청량을 기준으로 잡아요. 예를 들어, 쿠폰이벤트 오픈 직 후, 1만 명 유저가 10초 정도 안에 몰릴 것으로 예상한다면, 우선 1,000 RPS를 기준으로 테스트할 수 있습니다.보통은 예상치 하나만 테스트하지 않고 1,000, 2,000, 5,000 RPS처럼 부하를 단계적으로 높여봅니다. (예상 트래픽보다 좀 더 버퍼를 두어서 넉넉하게 테스트하면 좋으니까요) 그러면서 응답 시간과 오류율이 갑자기 증가하는 지점을 찾고, 예상 최대 트래픽에 재시도 처리 등을 고려한 여유까지 확보했는지 확인합니다.따라서 강의의 1,000 RPS와 5,000 RPS는 각각 예상 부하와 트래픽 급증 상황을 비교하기 위한 조건으로 참고해주시면 될 것 같습니다.좋은 질문 감사드립니다.
- Likes
- 1
- Comments
- 2
- Viewcount
- 97
Q&A
kafka 이벤트 발행 실패 시 at-least-once를 보장하는 방법이 궁금합니다.
안녕하세요 이찬원님!좋은질문 감사드립니다. 예리하게 잘 잡아주셨네요.말씀하신 대로 현재 코드는 Redis 차감 후 Kafka 발행 전에 장애가 발생하면 이벤트가 유실될 수 있습니다.따라서 신뢰성이 높은 구조를 만들기 위해, 여러 방법을 떠올려볼 수 있을 것 같아요. 첫번째로는 Redis 에서도 Outbox 패턴처럼 사용하는 방법을 사용하는 것입니다.Lua → Redis Stream → Connector → Kafka Topic같은 방식으로, Lua 스크립트(원자연산) 안에서 재고 차감 후, Redis Stream 으로 이벤트발행을 함께 실행합니다. 그리고 별도 워커나 Connector가 Stream을 읽어 Kafka에 발행합니다. 커넥터가 발행에 실패하거나 워커가 중단되면 메시지가 미처리 상태로 남으므로, 다시 처리할 수 있습니다. (at-least-once 등 여러 옵션을 지원합니다.)다만, 이 구조는 본 환경에서 검증이 필요하며, Redis에는 AOF와 복제를 적용하고, 처리되지 않은 Stream 메시지가 삭제되지 않도록 보존 정책도 관리해야 합니다. Redis 에 대한 관리포인트도 늘어납니다.두번째로는, 5단원 정합성에서 다루게 되는 대사입니다. (벌써 3단원이니 곧 만나실 것 같습니다.)주기적으로 Redis 와 DB 쿠폰발급 데이터를 점검하여 데이터를 바로잡는 방식인데요. 해당 단원에서도 위의 케이스는 저희가 다루지는 않지만, 위 문제의 경우 예를들어 이벤트가 재발행되도록 하는 조치를 해볼 수 있을 것 같습니다.사실 이런 문제를 해결하는 건 명확한 답이 있다기 보다는, 그 상황에 맞는 적절한 방법을 선택해서 구현하게 됩니다. 그래서 이 외에 다른 더 좋은 방법이 있을 수도있어요. 찬원님 질문덕에 저도 여러가지를 떠올려보다가 괜찮은 방법을 한번 답변드렸고, 실무라면 결국에는 좀 더 관리하기 편하고 신뢰성있는 방법을 고를 것 같습니다!감사합니다.
- Likes
- 2
- Comments
- 1
- Viewcount
- 116
Q&A
domain에 @Entity 와 Repository를 함께 둔 이유가 궁금합니다
안녕하세요 바나나님!결론부터 말씀드리면, 이 강의는 저희 강의 주제에 집중하고자 패키지 구조는 간단한 구성으로 진행하게 되었습니다.말씀하신 구성은 domain에는 순수한 도메인 모델만 두고, (인터페이스를 둔 뒤), 엔티티와 레포지토리를 storage 패키지 하위에 두는 패턴으로 이해했습니다. 반면에, 저희는 그것과는 다른 계열을 따랐는데요. domain 안에 엔티티객체를 그대로두고 사용했습니다. 말씀해주신 엔티티 매핑 비용도 물론 고려가 필요하지만, 그보다는 나눌 이유가 굳이 없는게 더 컸습니다. Coupon이나 Issuance 엔티티가 작아 도메인객체와 엔티티분리가 꼭 필요한 상황은 아니었고, 자주 변경되는 부분도 아니었고, 복잡한 구성보다는 간단한 구조로 실습하는게 더 유리하다고 판단했기 때문입니다.좋은 질문 감사합니다.
- Likes
- 1
- Comments
- 1
- Viewcount
- 98




