[SpringBoot - Ehcache]Spring 서비스에서 Ehcache를 도입하며 고민했던 것들 – 캐시는 성능이 아니라 운영 문제
·
ServerDev/SpringBoot
서비스를 운영하다 보면 자연스럽게 “이 요청, 굳이 매번 DB를 타야 할까?”라는 질문을 하게 됩니다.특히 공지사항 목록이나 질문 게시판처럼 조회 빈도는 높지만 변경은 상대적으로 적은 리소스의 경우, 요청이 몰릴수록 DB 부하가 눈에 띄게 증가하고 응답 시간도 점점 늘어나는 문제가 있었습니다. 초기에는 트래픽 자체가 많지 않았기 때문에 크게 체감하지 못했지만, 실제로 운영 환경에서 여러 서버가 동시에 요청을 보내기 시작하니 DB 쪽에서 병목이 생기는 지점이 보이기 시작했습니다.이때 가장 먼저 떠오른 해결책이 캐시였습니다. 다만, 캐시를 도입할 때부터 “무조건 Redis” 같은 선택을 하지는 않았습니다.현재 단계에서는 분산 캐시를 도입해 관리 포인트를 늘리기보다는, 서비스 내부에서 제어 가능한 범위의 캐시..
[SpringBoot] 로그에 찍힌 의문의 PROPFIND 요청, 해킹 공격일까? (feat. Spring Security)
·
ServerDev/SpringBoot
서버 로그에 찍힌 의문의 'PROPFIND', 온프레미스 서버를 지키기 위한 고군분투기평화롭게 서버 로그를 확인하던 중, 한 번도 본 적 없는 낯선 에러가 제 눈을 사로잡았습니다.운영 중인 서버 로그에 평소라면 보이지 않아야 할 IP와 생소한 HTTP 메서드가 기록되어 있더군요. 바로 이런 내용이었습니다. (그동안 회사 내부망에서만 테스트하던 프로젝트를 드디어 운영 환경으로 전환하고, 외부망에 공개했습니다. 이제 본격적인 운영인가 싶어 설레는 마음으로 로그를 확인하던 차에, 평소 보지 못했던 기괴한(?) 에러가.. ) org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the ..
[SpringBoot - gRPC] 실시간 STT 스트리밍과 세션별 gRPC 스트림 분리 (Project: SpeakNote)
·
ServerDev/SpringBoot
1. 왜 WebSocket 뒤에 gRPC 스트리밍이 필요한가SpeakNote는 사용자의 음성을 실시간으로 받아 텍스트로 변환하고, 그 결과를 다시 요약·주석 형태로 사용자에게 되돌려주는 시스템입니다. 이 과정에서 가장 중요한 요구사항은 “실시간성”과 “연속성”이었습니다. 사용자는 일정 간격으로 끊임없이 음성을 전송하고, 서버는 그 흐름을 끊지 않은 채 텍스트 결과를 받아야 합니다.브라우저와 서버 간의 통신은 WebSocket이 담당하지만, 음성 인식 자체는 외부 STT 엔진에 위임됩니다. SpeakNote에서는 Google Speech-to-Text의 Streaming API를 사용했고, 이 API는 gRPC 기반의 양방향 스트리밍 방식으로 동작합니다. 즉, 서버는 오디오 데이터를 지속적으로 전송하면서 ..
[SpringBoot - WebSocket] 실시간 음성 스트리밍 서버 구조 (Project: SpeakNote)
·
ServerDev/SpringBoot
들어가며 SpeakNote를 개발하면서 가장 먼저 부딪힌 문제는, 실시간 음성 입력을 서버에서 어떻게 받아야 안정적인 구조를 만들 수 있을지에 대한 고민이었습니다. 일반적인 웹 서비스처럼 요청이 들어오고 응답을 반환하는 구조와 달리, 음성 스트리밍은 사용자가 말을 하는 동안 아주 짧은 오디오 조각들이 끊임없이 서버로 전달되는 형태이기 때문에, 서버 입장에서는 “요청을 처리한다”기보다는 “흐름을 받아낸다”에 가깝습니다. 이 흐름을 제대로 설계하지 않으면, 서버는 생각보다 빠르게 병목 상태에 빠지게 됩니다. 이 글에서는 SpeakNote에서 Spring Boot 기반 WebSocket 서버를 어떻게 설계했고, 왜 그런 구조를 선택했는지를 중심으로 정리해보려 합니다. 1. 실시간 음성 입력의 특성과..
[Spring Boot 멀티모듈] domain-common 빌드 실패 원인과 해결 과정 정리 - Lombok, JPA @Entity 인식 안됨, Gradle 의존성 삽질기
·
ServerDev/SpringBoot
Spring Boot + Gradle 기반의 멀티모듈 프로젝트를 구성하던 중,공통 엔티티와 Enum, 도메인 객체를 모아둔 domain-common 모듈을 분리하여 관리하고 있었습니다. 프로젝트 구조는 다음과 같았습니다.Wecam-project├── domain-common│ └── build.gradle├── wecam-backend│ └── build.gradle├── wecamadminbackend│ └── build.gradle├── build.gradle (root)└── settings.gradle 이 구조에서 domain-common은@Entity@Table@EnumeratedLombok 어노테이션 (@Getter, @Builder 등) 을 포함한 순수 도메인 모듈 역할을 하고 있..
[Spring Boot] 사용자/관리자 API 분리를 위한 멀티모듈 설계 실전 가이드 (Common, User, Admin)
·
ServerDev/SpringBoot
[Spring Boot] 사용자/관리자 API 분리를 위한 멀티모듈 설계 실전 가이드 (Common, User, Admin)백엔드 개발을 하다 보면, 일반 사용자용 API와 관리자용(Back-office) API를 분리해야 할 때가 옵니다. 트래픽 패턴도 다르고, 보안 요구사항도 다르며, 배포 주기도 다르기 때문입니다.하지만 무턱대고 프로젝트를 2개로 쪼개면 User, Product 같은 엔티티와 도메인 로직을 양쪽에서 복사/붙여넣기 해야 하는 문제가 발생합니다.오늘은 domain-common(공통), wecam-backend(사용자), wecam-admin-backend(관리자)로 구성된 멀티모듈 아키텍처를 설계하고, 실제 build.gradle 설정부터 설정하면서 했던 고민(Repository/Ser..
[SpringBoot & FormData] JSON + 파일 업로드: HttpMediaTypeNotSupported 에러 해결
·
ServerDev/SpringBoot
단순한 해결법 나열이 아니라, 왜 에러가 발생했는지(HTTP 프로토콜 관점), 프론트엔드와 백엔드가 각각 어떻게 데이터를 주고받아야 하는지 원리를 포함하여 기록하려 합니당. (해당 트러블 슈팅은 wecam 프로젝트에서 발생함..) 프론트엔드(Next.js)와 백엔드(Spring Boot)를 연동하다 보면 가장 까다로운 구간 중 하나가 바로 "파일 업로드와 일반 데이터를 동시에 보낼 때"입니다.저는 두가지 방법을 고민했습니다. 두 번 요청: JSON 데이터를 먼저 보내서 ID를 받고, 그 ID로 파일을 별도 업로드한다. (Transaction 관리가 귀찮음)한 번 요청: multipart/form-data로 묶어서 한 방에 보낸다. (깔끔하지만 구현이 까다로움) 당연히 2번이 UX나 아키텍처 관..
[JPA] 동시성 제어 : LockModeType.PESSIMISTIC_WRITE
·
ServerDev/SpringBoot
Spring Data JPA를 사용하여 동시성 이슈를 해결하려 할 때, 가장 강력하면서도 확실한 방법인 LockModeType.PESSIMISTIC_WRITE(비관적 쓰기 락)에 대해 정리해 봅니다.1. PESSIMISTIC_WRITE란?LockModeType.PESSIMISTIC_WRITE는 데이터베이스의 배타적 락(Exclusive Lock) 기능을 사용하여 동시성을 제어하는 방식입니다.쉽게 비유하자면 다음과 같습니다."내가 이 데이터를 다 쓰고 나갈 때까지, 아무도 들어오지 마! (읽지도 말고 쓰지도 마!)"트랜잭션이 시작되고 데이터를 조회하는 순간부터 락을 걸어버리기 때문에, 데이터의 무결성이 깨질 확률을 '0'에 수렴하게 만드는 아주 강력한 옵션입니다. (대신 해당 lock 이 풀리기 전까진 다..
[SpringBoot&JPA] QR 코드 기반 게스트 계정 자동 생성 및 로그인 구현
·
ServerDev/SpringBoot
QR 코드 기반 게스트 계정 자동 생성 및 로그인 구현 (Spring Boot + JPA)QR 코드를 통해 서비스에 접근하는 게스트 사용자를 위해 인증 없이 계정을 자동 생성하고 바로 로그인시키고자 했습니다.기존 회원가입 로직은 이메일 인증과 key/code 검증이 필수라 게스트 흐름과 맞지 않았습니다.우선 자동 회원가입 -> 로그인 흐름을 맞추기 위해, 비밀번호 생성과 아이디 생성을 위한 Sequence Table 을 구현했습니다.해당 테이블로 유저가 동시 접속할 때 자동 +1 을 부여-- 시퀀스 생성CREATE SEQUENCE guest_seq START 1 INCREMENT 1 MINVALUE 1 NO MAXVALUE CACHE 1;-- 기존 guest_sequence_t..
[Spring Boot] 권한 검증 - 커스텀 AOP와 Redis로 해결하기 (@IsCouncil)
·
ServerDev/SpringBoot
권한 검증을 위한 커스텀 AOP 는 wecam 프로젝트를 진행하면서 만들게 되었습니다!! (프로젝트 기능 개발) 백엔드 개발을 하다 보면 컨트롤러 메서드마다 반복되는 검증 로직들이 있습니다."이 유저가 학생인가?", "헤더에 학생회 ID가 있는가?", "그 ID가 현재 로그인한 상태와 유효한가?" 이런 if 문들이 비즈니스 로직과 섞이면 코드가 지저분해지고 가독성이 떨어집니다. 오늘은 Spring AOP와 Redis를 활용해, 어노테이션 하나만 붙이면 이 모든 검증을 자동으로 수행하고 컨텍스트까지 주입해 주는 설계 패턴을 기록합니다, 1. 반복되는 검증 로직 학생회 관리 서비스를 만들었을 때, 특정 기능을 수행하려면 다음 과정이 필수적입니다.로그인한 유저인지 확인한다.유저의 Role이 STUDEN..
[SpringBoot - SSR] 파일 업로드 에러: "Failed to convert String to MultipartFile" 원인과 해결
·
ServerDev/SpringBoot
Spring Boot로 파일 업로드 기능을 구현하다 보면, 꽤 긴 에러 메시지와 함께 요청이 실패하는 경험을 하게 됩니다. 특히 폼(Form) 데이터를 통해 이미지를 전송할 때, 분명 파일을 선택했음에도 불구하고 서버 로그에는 타입 변환 실패(Type Mismatch) 오류가 찍히는 경우가 빈번합니다.오늘은 파일 업로드 구현 시 가장 흔하게 발생하는 MultipartFile typeMismatch 에러의 정확한 원인과, 이를 해결하기 위한 체크리스트를 정리해 보려 합니다. [SpringBoot - SSR] 파일 업로드 에러: "Failed to convert String to MultipartFile" 원인과 해결 Spring Boot에서 처음 파일 업로드 기능을 구현하다 보면,, 아래와 같은 에러 ..
[Spring Boot] 수정 기능 구현 시, 멀쩡한 데이터가 NULL 로 덮어씌워지는 문제
·
ServerDev/SpringBoot
[Spring Boot] 수정 기능 구현 시, 멀쩡한 데이터가 NULL로 덮어씌워지는 문제과거 멘토링 매칭 플랫폼 프로젝트 ( SSR로 구현 )를 진행하던 당시 겪었던 이슈를 기록으로 남기고자 합니다. 당시 회원 정보 수정 기능을 구현하고 테스트를 진행했는데, 분명 제목(Title)만 수정해서 저장했음에도 불구하고 카테고리나 인원수 같은 다른 필드들이 전부 NULL로 변해버리거나 데이터가 날아가는 현상이 있었습니다. 처음에는 단순한 쿼리 오류라고 생각했지만, 디버깅을 해보니 Spring MVC의 데이터 바인딩 원리와 JPA의 수정 방식을 제대로 이해하지 못해 발생한 구조적인 문제였습니다. 이번 글에서는 이 문제가 왜 발생했는지, 그리고 실무적으로 어떻게 해결하는 것이 가장 안전한지 정리해 보려고 합니다...
[ Web Stack] 스프링 부트 Web Stack 종류 - Server MVC & WebFlux
·
ServerDev/SpringBoot
Web Stack 종류java 언어로 구현하는 백엔드 서버의 프레임워크인 Spring Boot에서 스택을 나눌 수 있다는 것을 알게 되었습니다.스택을 나누고 구현을 한 적은 없는 것 같은데, 공부해보니 2가지 스택으로 나뉜다고 하더군요.항목Servlet 기반 (Spring MVC)WebFlux (Spring WebFlux)모델전통적인 동기/블로킹 I/O비동기/논블로킹 I/O스레드 모델요청 1개 = 스레드 1개요청 N개 ≠ 스레드 N개 (이벤트 루프 기반)리턴 타입String, ModelAndView, ResponseEntityMono, FluxServlet API 사용 여부사용 (예: HttpServletRequest)사용 안함공식 문서를 보았는데정확히 이해는 잘 되진 않지만, Model , Contro..