2월 26일 ~ 2월 28일까지 총 3일간 해커톤이 진행되었다. 이 기간 이전부터 나는 운이 좋게도 밥팟이라는 아이디어를 가진 팀에서 백엔드로 같이하자는 제안이 왔고 함께 하기로 했다. 처음에는 내 실력으로 피해를 끼치는 게 아닌가 하고 걱정이 많았지만 이러한 순간을 돌파할 때마다 성장한다는 생각하에 열심히 하자고 스스로 마음먹었다. 우리 팀은 풀스택 3명, 인공지능 2명, 클라우드 1명으로 이루어져 있었으며 카카오테크 부트캠프에서 교육생들끼리 밥을 같이 먹는 파티를 생성하고 참여할 수 있는 아이디어로 진행되었다. 내가 맡은 건 Springboot, MySQL(RDS)를 이용한 밥팟 서비스의 API개발이었다. 해커톤이다 보니 짧은 시간 안에 만들어야 하는 기능이 정해져 있어 일단 돌아가는 것에 초점을 맞..
전체 글
예상 독자MySQL의 공간 타입(sptial type)을 사용하고 있는데 공간 인덱스를 적용해보고 싶은 분공간인덱스가 적용이 안 되는 분핵심 요약MySQL의 공간인덱스를 적용하기 위해 SRID는 하나로 통일되어 있나요? (ex. 4326)공간 인덱스가 존재하더라도 옵티마이저의 선택을 받지 못하다면 풀스캔을 할 수 있습니다.문제 상황CareerBee에선 사용자의 현재 위치(위도, 경도) 기반으로 5KM 이내의 기업들을 조회하고 정보를 제공하고 있습니다. 이를 구현하기 위해 MySQL의 공감함수를 사용하고 있습니다. (아래 코드 참고)private BooleanExpression inDistance(String point, Integer radius) { return radius != null ..
Token 저장소를 RDB에서 Redis로 전환앞선 글에서 살펴본 것처럼 인증 및 인가가 필요한 API 요청 때마다 Token 테이블에 접근해 블랙리스트 여부를 확인하느라 DB I/O가 발생했고, 해당 쿼리는 Slow Query로 지정되기도 했습니다. 이 I/O 부하를 줄일 방안을 고민하다가 In-memory DB를 떠올렸고, 그중에서도 Redis를 선택하게 되었습니다. 물론 가장 빠른 방법은 Caffeine처럼 애플리케이션 내부 메모리를 캐시로 활용하는 것이겠지만,CareerBee는 현재 두 대 이상의 인스턴스로 운영되고 있습니다. 따라서 로컬 메모리를 쓰면 JVM 마다 토큰이 저장돼 인스턴스 간 토큰 정보를 공유할 수 없습니다. 따라서 전역(글로벌) 토큰 저장소가 필요했고, 이를 위해 Redis를 도..
문제 상황Signoz 상에서 Slow Query를 확인해 보니 아래의 두 쿼리가 상위권에 선정되었습니다.Token이 블랙리스트인지 확인 (7.10ms 소요)spring 인증객체로 두기위한 member select (8.18ms 소요) 크게 느린 속도는 아니지만 Slow Query로 선정됐으므로 개선을 진행해보고자 합니다.해결 방법위의 두가지 Slow Query는 인증이 필요한 모든 요청에서 사용되는데요. 아래와 같이 점진적으로 성능을 개선하고자 합니다.인증객체를 Member 엔티티 → 인증 DTO 로 변경MySQL → Redis로 토큰 저장소 변경인증객체를 Member에서 DTO로 변경현재 인증 및 인가를 위해 Spring Security + JWT를 혼합하여 사용하고 있습니다. 서비스 코드에서 인증객체..
CareerBee 서비스에서 백엔드 개발을 맡고 있는 mumu입니다. 이번 글에서는 대회 참여 후 알림은 송신되지만 저장되지 않는 문제를 해결하며 배운 것들에 대해 다뤄보고자 합니다.예상 독자해당 글은 아래와 같은 독자들에게 도움이 될 것입니다.Spring의 @Transactional을 사용했지만 내부 동작방식을 몰라요.@TransactionalEventListener를 사용하며 DB CUD 연산이 적용되지 않는 문제를 겪고 있어요.핵심 요약@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)에서 DB CUD 작업이 적용되지 않는 문제 발생@Transactional 내부 동작방식에 따라 AFTER_COMMIT 이후 DB 트랜잭션은 끊어져 ..
CareerBee에서는 다음과 같은 기능에서 AI 서버와의 통신이 이뤄지고 있습니다.이력서 및 고급 이력서 생성이력서 정보 추출면접 문제에 대한 AI 피드백 제공현재 해당 기능들은 동기(Synchronous) 방식으로 구현되어 있어, 동시에 많은 요청이 발생할 경우 처리 속도가 느려지거나 요청이 실패하는 문제가 발생할 수 있습니다. 이러한 문제를 해결하고 병렬 처리 성능을 높이기 위해, 비동기(Asynchronous) 통신 방식으로 전환을 진행하고자 합니다. 이를 위해 기존에 사용하던 RestClient에 CompletableFuture 기반의 비동기 처리를 도입하여, 대량의 요청에도 안정적으로 대응할 수 있는 구조로 개선할 예정입니다.비동기 처리결과는 어떻게 프론트에 전달할까?코드를 바로 적용하기에 앞..
기업의 마커 정보 조회의 성능을 높이고자 캐싱을 적용하고 있습니다. 이를 위해 직접 Redis에서 데이터를 get 해오고 없다면 put 해오는 코드를 다음과 같이 작성하였습니다.@Overridepublic CompanyMarkerInfo fetchCompanyLocation(Long companyId) { RBucket cachedCompanyMarkerInfo = redissonClient.getBucket( GEO_KEY_PREFIX + companyId ); String cached = cachedCompanyMarkerInfo.get(); if (cached != null) { try { return objectMapper.read..
Careerbee에 신기능을!CareerBee 서비스에 곧 포인트 상점이 들어옵니다!포인트를 사용해 응모권(티켓)을 구매할 수 있는데요티켓은 매주 100개씩 충전되며, 유저들은 한정된 티켓수 안에서 선착순으로 구매해야 합니다. 이를 위해 백엔드 개발자인 저는 열심히 티켓 구매 기능을 만들고 있습니다..문제 발생기능은 만들었습니다. 바로 아래와 같이 말이죠@Overridepublic void purchaseTicket(TicketPurchaseReq ticketPurchaseReq, Long accessMemberId) { Member member = memberQueryService.findById(accessMemberId); Ticket ticket = storeQueryService.fi..
1월부터 현재까지 약 5개월이 흘렀고, 카카오테크 부트캠프에 몰입하며 시간을 보내고 있습니다! 5개월간 얼마나 성장했는지 감은 안 잡히지만, 개발자로서 크게 성장한 기간이라고 생각합니다 😁 이 글은 5개월간 어떤 경험을 했고, 부트캠프를 고민하고 있는 예비 개발자분들에게 카카오테크 부트캠프를 강력하게 영업하는 글이 될 것입니다. 판교 교육장으로의 출근 시작3월 말부터 본격적으로 판교 교육장으로 출근(?)을 하게 되었습니다. 대한민국 IT 중심인 판교를 예비 개발자의 신분으로 간다니... 너무 설레고 기대가 가득했던 게 기억이 나네요 ㅎㅎ(물론 지금은 판교.... 출퇴근 너무 힘들어요... 🤣) 교육장에서 제 자리도 배정받고!! 저의 첫 팀인 육개장과 함께 모여서 열심히 공부를 했고, 개인프로젝트를..
안녕하세요. CareerBee 서비스에서 백엔드 개발을 맡고있는 mumu입니다. 해당 글은 기업 검색시 발생한 문제에 대해서 설명하고 어떻게 해결했는지를 서술하고 있습니다.🔍 문제점CareerBee에는 사용자가 입력한 키워드로 기업을 검색하는 기능이 있습니다. 문득, 이름에 _(언더바) 를 포함한 기업이 있는지 궁금해 "_"를 검색해봤습니다. 하지만 결과는 엉뚱했습니다. 언더바가 들어가지 않은 기업들이 검색되는 것이었습니다.❓ 왜 이런 현상이 발생했을까?결론부터 말하자면 LIKE 쿼리에서 특수문자 _를 escape 처리하지 않았기 때문입니다. 검색 결과를 가져오는 Repository 의 코드를 보면 다음과 같이 LIKE 구문이 사용됨을 볼 수 있습니다.@Overridepublic CompanySear..