[SpringBoot - Ehcache]Spring 서비스에서 Ehcache를 도입하며 고민했던 것들 – 캐시는 성능이 아니라 운영 문제
·
ServerDev/SpringBoot
서비스를 운영하다 보면 자연스럽게 “이 요청, 굳이 매번 DB를 타야 할까?”라는 질문을 하게 됩니다.특히 공지사항 목록이나 질문 게시판처럼 조회 빈도는 높지만 변경은 상대적으로 적은 리소스의 경우, 요청이 몰릴수록 DB 부하가 눈에 띄게 증가하고 응답 시간도 점점 늘어나는 문제가 있었습니다. 초기에는 트래픽 자체가 많지 않았기 때문에 크게 체감하지 못했지만, 실제로 운영 환경에서 여러 서버가 동시에 요청을 보내기 시작하니 DB 쪽에서 병목이 생기는 지점이 보이기 시작했습니다.이때 가장 먼저 떠오른 해결책이 캐시였습니다. 다만, 캐시를 도입할 때부터 “무조건 Redis” 같은 선택을 하지는 않았습니다.현재 단계에서는 분산 캐시를 도입해 관리 포인트를 늘리기보다는, 서비스 내부에서 제어 가능한 범위의 캐시..
[FastAPI 운영 로그 설계] 로그 포맷과 클라이언트 IP
·
ServerDev/FastAPI
우선 현재는 개발단계기 때문에 모니터링 환경 (Prometheus/Grafana, ELK, Cloud Logging 등등,,,) 전혀 구축을 하지 않았으며, 클라우드 인프라를 이용하지 않고 온프레미스를 구축 중입니다.즉 , 로깅을 직접 해야된다는 뜻..사실 개발은 여러 개발자, 여러 서버를 각자 개인의 개발자가 확인해서 문제를 잡으면 되지만,, 전체 흐름에서 언제 터지는지 확인을 하는게 에러 잡기 가장 편하더라구요. 또한, 저희는 엔진엑스도 분리된 서버에 있고, 요청이 여러 대의 서버를 거쳐 들어오기 때문에“어디서 터졌는지”를 감으로 찾는 게 불가능해집니다. 특히 운영 중에는 “에러가 났다”보다 더 무서운 게, 에러가 났는데 어느 지점에서 문제가 시작됐는지 추적이 안 되는 상황이더라구요. 그래서 저는 이..
[DataEngineering - MongoDB] 실시간 주석 스냅샷 스키마 설계 (Project: SpeakNote)
·
ServerDev/Database
1. 실시간 주석 데이터는 왜 기존 DB 모델로 다루기 어려운가SpeakNote에서 다루는 주석 데이터는 일반적인 CRUD 중심의 데이터와 성격이 완전히 다릅니다.주석은 한 번 생성되고 끝나는 데이터가 아니라, 실시간 음성 인식 결과와 AI 요약 결과가 누적되며 계속 변화하는 흐름형 데이터에 가깝습니다.하나의 강의 파일에 대해 사용자는 다음과 같은 행동을 반복합니다.음성을 입력하고일정 시간 뒤 AI 주석이 생성되고주석이 PDF 위 특정 좌표에 배치되고필요하면 다시 수정하거나 삭제합니다.이 과정에서 중요한 점은, “현재 상태”뿐 아니라 “그 시점의 상태” 자체가 의미를 가진다는 점입니다.즉, SpeakNote에서의 주석은 단순한 레코드가 아니라, 시간에 따라 변화하는 결과 묶음으로 봐야됐습니다.2. Spe..
[DataBase - Design] MySQL + MongoDB를 분리한 이유 (Project: SpeakNote)
·
ServerDev/Database
1. SpeakNote에서 “저장”이 성능 병목이 되는 지점SpeakNote는 단순히 텍스트를 저장하는 서비스가 아니라, 브라우저에서 들어오는 음성 청크를 실시간으로 받아서 STT로 변환하고, 그 결과를 다시 요약·주석 형태로 가공해 UI에 즉시 반영하는 흐름을 갖고 있습니다. 이때 사용자가 체감하는 품질은 “정확도”도 중요하지만, 실제로는 끊기지 않고 계속 흘러가는지, 그리고 응답 지연이 특정 순간에 폭발하지 않는지가 훨씬 크게 작동합니다. 실시간 시스템은 어느 한 구간이라도 길게 막히면 그 뒤의 모든 단계가 연쇄적으로 밀려서, 결국 사용자는 “가끔 멈추는 서비스”로 인식하게 되기 때문입니다. 문제는 이 파이프라인에서 저장 계층이 생각보다 쉽게 병목이 된다는 점입니다. 주석은 단순 텍스트 한 줄이 아..
[FastAPI - SSE Streaming] FastAPI SSE 스트리밍(StreamingResponse) 실전 적용기: 동기 generator를 비동기로 “진짜 스트리밍” 만들기
·
ServerDev/FastAPI
사내 챗봇/LLM 기능을 붙이면서, 답변을 한 번에 반환하는 게 아니라 토큰(혹은 chunk) 단위로 프론트에 흘려주는 SSE(또는 스트리밍 응답)를 구현해야 했습니다!(기존에 MVP 용으로 개발했던 FastAPI 서버를 다 버리고 다시 새롭게 구현..ㅜㅜ.이제는 처음부터 끝까지 제가 하나씩 고쳐가고 패키지 구조를 잡고, common 모듈로 분리할 수 있고,, 재사용성을 고려한 코드 설계를 할 수 있다는 장점이 보였지만..Spring 쪽을 하며 Fast 쪽도 하려니 조금 힘들 거 같아, SpringBoot 추가 기능 개발은 제 손을 떠나갔습니다..다만 FastAPI 로 MVC를 구현하는 것은 좀 오랜만이라 공부하며 하다보니 느릿느릿하게 개발하게 된 듯.. ) FastAPI에서 StreamingRespon..
[SpringBoot] 로그에 찍힌 의문의 PROPFIND 요청, 해킹 공격일까? (feat. Spring Security)
·
ServerDev/SpringBoot
서버 로그에 찍힌 의문의 'PROPFIND', 온프레미스 서버를 지키기 위한 고군분투기평화롭게 서버 로그를 확인하던 중, 한 번도 본 적 없는 낯선 에러가 제 눈을 사로잡았습니다.운영 중인 서버 로그에 평소라면 보이지 않아야 할 IP와 생소한 HTTP 메서드가 기록되어 있더군요. 바로 이런 내용이었습니다. (그동안 회사 내부망에서만 테스트하던 프로젝트를 드디어 운영 환경으로 전환하고, 외부망에 공개했습니다. 이제 본격적인 운영인가 싶어 설레는 마음으로 로그를 확인하던 차에, 평소 보지 못했던 기괴한(?) 에러가.. ) org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the ..
[FastAPI] 30명 동시 접속 RAG 챗봇, 왜 FastAPI 비동기(Async)가 필수일까?
·
ServerDev/FastAPI
[FastAPI] 30명 동시 접속 RAG 챗봇, 왜 FastAPI 비동기(Async)가 필수일까?일반적인 웹 서비스의 응답 속도는 0.1초 내외로 매우 빠릅니다. 하지만 LLM(거대 언어 모델) + RAG(검색 증강 생성) 기반의 챗봇은 이야기가 다릅니다. 답변 하나를 만드는 데 짧게는 3초, 길게는 30초 이상 걸리기 때문입니다.만약 30명이 동시에 질문을 던진다면 서버 내부에서는 어떤 일이 벌어질까요? 왜 여기서는 '비동기(Async)'가 선택이 아닌 필수인지 , 멀티쓰레드와 싱글쓰레드의 정확한 차이가 무엇인지, 어떤식으로 요청을 접수시켜야될지? 기록해보려 합니다.1. 상황 분석: 30명의 접속, 응답 시간 30초RAG 챗봇의 프로세스는 보통 3단계를 거칩니다.질문 임베딩: 질문을 벡터로 변환 (C..
[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. 실시간 음성 입력의 특성과..
[FastAPI/AI] OCR, LLM(Ollama), RAG 구현 시 절대 BackgroundTasks만 믿으면 안 되는 이유
·
ServerDev/FastAPI
FastAPI는 Python 기반이라 AI 모델 서빙용으로 많이 사용됩니다. (우선 제가 보통 FastAPI 를 그런 용도로 쓰기 때문에...ㅎㅎ)개발하다 보면 자연스럽게 이런 욕심이 생깁니다."사용자가 PDF를 올리면, BackgroundTasks로 넘겨서 OCR 돌리고 벡터 DB에 넣으면 되겠지?" 결론부터 말씀드리면, 토이 프로젝트면 괜찮지만 , 실무라면 서버가 위험해지더라구욥.... (갑자기 끊김..) 단순한 I/O 작업(이메일)과 CPU/GPU 집약적 작업(AI Inference)의 차이 때문일까요? 그런 거 같습니다.1. AI 작업, 기다리기??BackgroundTasks가 가볍고 좋은 이유는 주로 I/O Bound(네트워크 대기, 디스크 쓰기) 작업에 최적화되어 있기 때문입니다. 하지만 A..
[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..
[FastAPI] 미들웨어 - 작동 원리부터 ELK 연동을 위한 JSON 구조화 로깅까지
·
ServerDev/FastAPI
[FastAPI] 미들웨어 - 작동 원리부터 ELK 연동을 위한 JSON 구조화 로깅까지FastAPI로 백엔드를 개발하다 보면 "모든 요청의 처리 시간을 재고 싶다"거나 "들어오는 모든 요청의 Body 데이터를 로그로 남기고 싶다"는 욕심이 생깁니다. 오늘은 FastAPI의 숨은 조력자 미들웨어(Middleware)의 모든 것을 파헤쳐 보고, 실무에서 ELK Stack이나 Sentry와 연동 가능한 'JSON 구조화 로깅' 미들웨어를 직접 구현해 보겠습니다.1. 미들웨어 아키텍처 클라이언트의 요청(Request)은 핵심 로직(API 엔드포인트)에 도달하기 위해 여러 겹의 미들웨어를 뚫고 들어갑니다. 처리가 끝나면 응답(Response)은 다시 그 미들웨어들을 거꾸로 통과해서 나갑니다.주의: 등록 순서..
[실전 Django - database 정복기 5부] 인덱스(Index) 설계: "쿼리가 느리다면 답은 정해져 있다"
·
ServerDev/DJango
네, 대망의 완결편입니다. 백엔드 성능 최적화의 '끝판왕'이자, 아무리 코드를 잘 짜도 이걸 모르면 말짱 도루묵이 되는 데이터베이스 인덱스(Index)에 대한 이야기입니다. (사실 제 기준 끝판왕이지,, 새로 추가될게 언젠가는 있을 거 같습니다.. )Django ORM은 db_index=True 옵션 하나로 인덱스를 너무 쉽게 만들 수 있게 해주지만, 그 이면에 숨겨진 작동 원리와 함정까지 알려주지는 않습니다. 쿼리 속도를 0.01초로 만드는 마지막 열쇠를 쥐어봅시다. 인덱스(Index) 설계: "쿼리가 느리다면 답은 정해져 있다"1. 100만 건 중 1개를 찾는 방법지난 시리즈들을 통해 우리는 N+1 문제를 해결하고, Bulk 연산으로 대량 처리를 최적화했습니다. 그런데 데이터가 100만 건, 1,0..