Nori 형태소 분석기 Stop Filter 문제 — "안녕" 0건과 "안녕하세" 노이즈 해결
Lucene Nori 분석기에서 "안녕" 검색이 0건이 되는 IC 필터링 문제와, "안녕하세" 검색 시 "하세" 관련 문서만 나오는 형태소 분석 한계를 분석합니다. IC 제거 + title_ngram dis_max + PrefixQuery 폴백 3단계 해결을 적용하고, 자동완성 title_raw fallback까지 포함합니다.
검색 결과가 없습니다
제목, 태그, 카테고리로 검색
Lucene Nori 분석기에서 "안녕" 검색이 0건이 되는 IC 필터링 문제와, "안녕하세" 검색 시 "하세" 관련 문서만 나오는 형태소 분석 한계를 분석합니다. IC 제거 + title_ngram dis_max + PrefixQuery 폴백 3단계 해결을 적용하고, 자동완성 title_raw fallback까지 포함합니다.
카카오톡 메시지와 마켓플레이스 리뷰를 통해, CodingTestKit이 실제로 누군가에게 도움이 되고 있다는 걸 느끼며 개발자로서 뿌듯함을 느낀 기록입니다.
인디게임 밸런싱 도구 PowerBalance를 만들며 느꼈던 가능성과, 실제 사용자 반응이 기대와는 달랐던 경험, 그리고 그 배움이 이후 CodingTestKit에 어떻게 이어졌는지를 정리한 글입니다.
나무위키+한국어 위키백과+영어 위키백과+뉴스+웹텍스트+C4 한국어 코퍼스 1,215만 건 검색 엔진 프로젝트를 2개월간 26편의 기술 블로그로 기록하고 총정리합니다. MySQL LIKE 5,000ms 타임아웃에서 시작하여 임베디드 Lucene + Nori 한국어 형태소 분석으로 전환하고, Caffeine+Redis 2계층 캐시(82% 히트율), MySQL Replication R/W 분리, Nginx 스케일아웃(에러율 13.25%→0%), Debezium+Kafka CDC, Redis 3노드 Consistent Hashing까지 분산 아키텍처를 완성합니다. 검색 품질은 동의어 확장, 오타 교정, UnifiedHighlighter snippet, LTR(NDCG +4.8%p), 카테고리 28개 자동 분류, Aho-Corasick 금칙어 필터링으로 고도화하고, RAG(Gemini SSE 스트리밍)로 AI 검색 요약을 제공합니다. 자동완성 시스템 설계(CQRS + MapReduce + CDC)의 이론과 실제 구현의 매핑, 26편 전체 시리즈 링크, 핵심 수치 총정리를 포함합니다.
Lucene BM25 검색 결과 Top-5 문서를 LLM 컨텍스트에 주입하는 RAG(Retrieval-Augmented Generation) 파이프라인을 구축합니다. Spring AI 2.0 + Gemini 2.0 Flash로 SSE 스트리밍 답변을 생성하고, 인라인 출처 배지를 파싱하여 게시글 링크로 연결합니다. 할루시네이션 방지(문서 기반 답변 제한 + 인용 강제), AI 요약 트리거 조건(네비게이션 의도 스킵), Redis Token Bucket rate limiting(10 RPM 전역), 동일 쿼리 캐싱(TTL 30분, LLM 비용 40-60% 절감), Grafana 7패널 대시보드(RPM, 응답시간, 토큰, 피드백, 비용 추정)까지 포함합니다. BM25가 이 프로젝트에서 Dense Retrieval보다 적합한 근거와, Hybrid Retrieval 전환 로드맵도 정리합니다.
커뮤니티 검색 서비스의 운영 안전장치를 구축합니다. 16,090개 금칙어를 Aho-Corasick O(N+Z) 알고리즘으로 탐지하여 자동완성 결과에서 유해 검색어를 필터링하고, 블라인드 게시글을 Lucene Occur.MUST_NOT으로 검색에서 제외합니다. 영어 금칙어의 Scunthorpe 문제(단어 경계 매칭), Negative Caching(빈 결과 30초 TTL)으로 cache penetration 방지, title_raw StringField로 자동완성 Lucene fallback 품질을 개선합니다.
LLM 워크플로우를 실행하고, 실패를 분석하고, 규칙을 자동 생성하고, 회귀 테스트까지 하나의 루프로 연결하는 평가 엔진을 만들었습니다.
BM25 수동 가중치(title:3, content:1)의 한계를 Learning to Rank(LTR)로 극복합니다. 카테고리 28개 자동 분류(키워드 기반, 정확도 83%) → SortedSetDocValuesFacetCounts 네이티브 Facet 전환 → 태그 216만 건 인덱싱을 1회 재색인으로 통합 반영합니다. LLM-as-a-Judge(Gemini)로 학습 데이터 900쌍을 생성하고(1차 실패 98% → 5초 딜레이+지수 백오프로 해결), XGBoost LambdaMART 14개 피처로 학습하여 NDCG@10을 0.6910 → 0.7387(+4.8%p) 개선합니다. XGBoost4J ARM64 네이티브 추론, Rescorer Top-200 재랭킹, RefreshListener 기반 FacetState 캐싱, MultiCollectorManager 단일 패스 수집까지 구현하지만, 2코어 ARM Free Tier에서 LTR ON 시 CPU 포화(72배 악화)를 k6로 실측하여 LTR_ENABLED=false로 비활성화합니다.
Lucene 기반 검색 엔진의 Recall과 Precision을 동시에 개선합니다. 동의어 확장(DB 기반 쿼리 타임)으로 "AI" 검색 시 "인공지능" 문서를 포함시키고, DirectSpellChecker로 "프로그래링" → "프로그래밍" 오타 교정을 구현합니다. UnifiedHighlighter + snippetSource 500자 StoredField로 검색어 주변 맥락 snippet을 제공하고, 무중단 전체 재색인 인프라(Directory Swap + SearcherManager 재생성)를 구축하여 12,156,589건(42GB)을 ~2시간 만에 재색인합니다. 인덱스 타임 동의어가 IDF를 왜곡하는 원리, Nori 사용자 사전 158,539개 적용, BM25 변형(BM25+/L/F) 불필요 판단 근거까지 정리합니다.
1,425만 건 Lucene 검색 엔진에 카테고리 필터링을 추가합니다. categoryId가 이미 LongField로 인덱싱되어 있지만 검색 쿼리(buildQuery)에서 사용하지 않고 있던 구조적 비대칭을 발견하고, Occur.FILTER 절로 해결합니다. DB Post-filter 방식이 pagination을 깨뜨리는 이유, FILTER가 MUST와 달리 스코어에 기여하지 않으면서 bitset 캐싱 대상이 되는 원리, DB GROUP BY 간이 Facet의 한계와 Lucene 네이티브 Facet 전환 계획까지 정리합니다.
단일 서버에서 100-150 VU가 한계였던 시스템을 분산 아키텍처(2 App + MySQL Replication + Redis 3샤드 + Kafka CDC)로 전환한 후, stress 테스트(200 VU, 25분)로 한계점을 재탐색합니다. 100 VU에서 P95 200ms(SLA 충족), 200 VU에서 에러율 0.09%(단일 서버 13.25% → 0.09%), 처리량 109 req/s(3.6배↑). App CPU가 여전히 근본 병목임을 소거법으로 확인하고, MySQL/Redis/Kafka/Nginx 모두 여유임을 실측합니다.
단일 Redis 인스턴스에서 KEYS 블로킹(34.6ms), 배치↔실시간 워크로드 간섭(GET 최악 15.5ms), volatile-lru 보안 위험을 실측하고, KEYS→SCAN 전환 + 3노드 Consistent Hashing + 블랙리스트 전용 인스턴스 격리로 해결합니다. 가상 노드 150개 ConcurrentSkipListMap 라우팅, 노드 장애 시 Lucene fallback, 100 VU 부하 테스트로 검증한 과정을 정리합니다.
PostService의 dual-write 구조(MySQL + Lucene 직접 호출)가 데이터 불일치, 강결합, 불완전한 캐시 무효화를 유발하는 문제를 점진적으로 해결합니다. Spring ApplicationEvent로 디커플링 → @ApplicationModuleListener 비동기 전환(쓰기 5,315ms→33ms) → Debezium + Kafka CDC로 binlog 기반 모든 변경 캡처까지. 100 VU 부하 테스트로 각 전환을 검증하고, dual-write를 원천 차단하여 검색 인덱스 정확성을 보장합니다.
GET 요청에 포함된 DB UPDATE가 R/W 분리와 충돌하여 500 에러가 발생한 문제를 Redis INCR + 30초 배치 flush로 해결합니다. REQUIRES_NEW, 비관적/낙관적 락, @Async, Caffeine 로컬 카운터 등 5개 대안을 비교 분석하고, Write-Behind 패턴으로 GET에서 DB 쓰기를 완전히 제거하여 에러율 11.10% → 0.00%, 상세 조회 응답시간 36% 개선을 달성합니다. Sentry·YouTube 등 현업 사례와 비용 분석, 면접 Q&A까지 포함합니다.
App CPU 100% 병목을 해소하기 위해 App 인스턴스를 2대로 확장합니다. Nginx map 기반 HTTP 메서드 라우팅(least_conn), Lucene Primary/Replica 모드 분리(SnapshotDeletionPolicy + Refresh Pause + rsync), TokenBlacklist Redis 전환, 조회수 Redis INCR 배치 flush 전환까지 — 100 VU 기준 에러율 13.25%→0.00%, P95 2,300ms→158ms, 평균 482ms→37ms로 개선합니다.
MySQL Replication으로 읽기/쓰기를 분리하고, Spring AbstractRoutingDataSource + LazyConnectionDataSourceProxy로 @Transactional(readOnly=true) 기반 자동 라우팅을 구현합니다. CLONE PLUGIN으로 133.5GB 초기 동기화, HikariCP 풀 분리(Primary 5 + Replica 15), k6 100 VU load 테스트로 R/W 분리 실측까지 정리합니다.
Caffeine(L1) + Redis(L2) 2계층 캐시를 구현하고, Trie 자동완성을 Redis flat KV O(1) GET으로 전환하여 Stateless 앱을 만든 뒤 k6 부하 테스트로 Before/After를 비교한 과정을 정리합니다. 분산 전환 순서(Redis → Replication → 스케일아웃)의 의존 관계와 비용 분석을 포함합니다.
k6 stress 테스트(200 VU, 25분)로 단일 서버(ARM 2코어, 12GB)의 한계점(~100-150 VU)을 수치로 확인하고, JVM/Tomcat 튜닝이 CPU-bound 병목에서 역효과를 낸 과정과 배포 미반영 사고를 기록합니다.
Lucene PrefixQuery의 사전순 자동완성을 검색 로그 기반 인메모리 Trie로 전환하여 인기순 정렬 + 한글 자모 중간 입력("삼ㅅ" → "삼성전자") + 초성 검색("ㅅㅅ" → "삼성전자")을 구현한 과정을 정리합니다.
Caffeine 로컬 캐시(L1)를 도입하여 검색/자동완성/상세 조회를 캐싱하고, @CacheEvict 무효화, Cache-Control 브라우저 캐싱, Actuator 모니터링까지 구현한 뒤 k6 부하 테스트로 Before/After를 비교한 과정을 정리합니다.
PhraseQuery(slop=2)로 구절 검색을 구현하고, FeatureField 기반 BM25 + 인기도 + 최신성 커뮤니티 랭킹을 적용한 뒤, P@10/MAP 지표로 검색 품질을 정량 평가한 과정을 정리합니다.
1,215만 건 테이블에서 COUNT(*) 제거(Page→Slice), 30페이지 제한, Deferred Join을 조합하여 최신 게시글 목록 조회를 19,424ms에서 8.33ms로 개선하고, k6 load 테스트(100 VU, 20분)에서 에러율 32.53%→0%를 달성한 과정을 정리합니다.
1,475만 건 OFFSET 페이지네이션에 Deferred Join을 적용하고, EXPLAIN으로 기대만큼 빠르지 않은 이유를 분석합니다. Slack·Twitter 등 실서비스의 Keyset Pagination 사례와 비교하며 다음 단계를 도출합니다.
코딩테스트 준비의 불편함을 해결하기 위해 IntelliJ와 VS Code에서 문제 가져오기, 로컬 테스트, 제출까지 한 번에 할 수 있는 플러그인을 만들었습니다.
MySQL FULLTEXT ngram의 구조적 한계(고빈도 토큰 타임아웃, 300GB+ 인덱스, false positive)를 분석하고, Lucene·Elasticsearch·벡터DB를 비용 관점에서 비교하여 임베디드 Lucene + Nori 형태소 분석기를 선택한 기술 결정 과정을 정리합니다.
Astro 기반 정적 블로그를 직접 설계하고 구축한 과정을 정리했어요. TinaCMS 연동, i18n, 카테고리 시스템, 다크모드 등 주요 기술 결정을 다뤄요.
게임 기획자를 위한 오픈소스 밸런싱 플랫폼 인디밸런싱의 개발 과정과 GDC/NDC 이론 학습 경험을 정리했습니다.
B-Tree 인덱스의 한계를 넘어 FULLTEXT ngram 역색인으로 LIKE 검색을 대체하고, 57만 건 한국어 데이터에서 12초→6ms로 약 2,100배 성능을 개선한 과정과 ngram의 알려진 한계점을 정리한다.
게이미피케이션 기반 집중력 타이머 서비스 타이미의 개발 동기, Linear-GitHub-Slack 자동화, 모바일 OAuth 인증, 코드 품질 파이프라인을 정리했습니다.
자동완성 LIKE prefix 검색이 인덱스 없이 Full Table Scan으로 타임아웃되는 문제를 B-Tree 복합 인덱스(title, view_count DESC)로 해결하고, 단일 인덱스 대안과 커버링 인덱스, Trie 자료구조를 비교 검토한 과정을 정리한다.
LIKE 검색이 Full Table Scan으로 1,215만 행을 스캔하며 HikariCP 커넥션 풀을 고갈시켜 시스템을 마비시킨 원인을 분석하고, 긴급 완화 조치로 시스템 안정성을 확보한 과정을 정리한다.
실제 사용자 피드백을 바탕으로 입력값 손실 버그, 드래그 성능, ESC 처리, 키보드 네비게이션 등 24개 항목을 개선한 과정을 정리했어요.
나무위키, 위키피디아 덤프 데이터를 MySQL에 적재하고, 커뮤니티 수준의 트래픽을 감당할 수 있는 검색엔진을 만드는 프로젝트의 개요와 서버 구성을 정리한다.
MVP로 공개한 인디밸런싱의 계산기, 성장곡선, 전투 시뮬레이션, 밸런스 분석 등 15가지 핵심 기능을 소개해요.
React 스프레드시트에서 IME 조합 입력 시 자음/모음 분리 문제를 Uncontrolled Component와 Composition 이벤트로 해결한 과정을 정리했어요.
debounce에서 rafThrottle, 최종적으로 즉시 동기화까지 오픈소스 분석을 통해 도달한 셀-수식바 동기화 최적화 과정을 정리해 봤어요.
CVSS 9.9 Redis 취약점(CVE-2025-49844 RediShell)을 발견하고, 긴급 패치 적용과 다층 보안 강화 조치를 수행한 과정을 정리한다.
Spring Boot 4의 API Versioning이 Swagger UI와 충돌하는 문제의 원인을 분석하고, ApiVersionCustomizer로 해결한 과정을 정리한다.
React 스프레드시트에서 드래그 선택 시 발생한 성능 병목을 Set 자료구조, RAF 기반 throttle, DOM 직접 조작으로 해결한 과정을 정리했어요.
SimpleBroker의 수평 확장 한계를 Redis 세션 관리로 해결하고, LocalDateTime→Instant 마이그레이션, 커서 기반 페이지네이션, SockJS Fallback 등 트러블슈팅을 정리한다.
Spring Boot 4.0 / Spring Framework 7.0에서 새로 도입된 프레임워크 레벨 API 버전 관리 기능의 설정과 동작 원리를 정리한다.
Redis Pub/Sub의 Fire-and-Forget 특성으로 WebSocket 재연결 시 메시지가 유실되는 문제를 MongoDB 기반 커서 페이지네이션으로 해결한 과정을 정리한다.
Soft Delete + 배치 스케줄러로 고아 파일을 자동 정리하는 시스템 설계와, @TransactionalEventListener AFTER_COMMIT의 트랜잭션 함정을 정리한다.
인디게임 개발자를 위한 웹 기반 게임 밸런스 데이터 관리 툴의 기능, 수식 체계, 시뮬레이션, 게임 엔진 연동을 소개해요.
Spring WebSocket STOMP의 Inbound Thread가 I/O 대기로 블로킹되는 문제를 분석하고, Kotlin Coroutine으로 스레드를 즉시 반환하도록 개선한 과정을 정리한다.
웹의 HttpOnly Cookie 인증과 모바일 앱 인증의 차이, 모바일에서 JWT + Redis Refresh Token 전략을 설계한 과정을 정리한다.
레이어 간 객체 변환에서 수동 매핑의 문제를 분석하고, MapStruct로 컴파일 타임 매핑 코드를 자동 생성하는 전략을 정리한다.
Spring MVC에서 Coroutine의 withContext로 스레드가 전환되면서 Hibernate Session과 SecurityContext가 유실되는 문제를 분석하고, runBlocking + Fetch Join으로 해결한 과정을 정리한다.
2,200명 규모 인디게임 개발 커뮤니티에서 설문조사와 피드백 분석을 통해 게임 밸런스 툴의 시장 수요를 검증한 과정을 정리했어요.
Spring Security에서 로그인 사용자 정보를 가져오는 방법을 비교하고, @CurrentUser 커스텀 어노테이션으로 의존성을 관리한 과정을 정리한다.
채팅 메시지 전송 시 매번 MySQL을 조회하던 권한 체크를 Redis 캐싱으로 최적화하고, lastMessage 업데이트를 비동기로 처리하여 응답 시간을 2배 개선한 과정을 정리한다.
Spotless, Checkstyle, SpotBugs, JaCoCo, Codecov를 조합한 무료 코드 품질 관리 파이프라인 구축 과정을 정리한다.
N+1 Query로 1.3초 걸리던 채팅방 목록 조회를 Fetch Join + 배치 조회 + Redis 캐싱(MGET)으로 85ms까지 최적화한 과정을 정리한다.
JPA ddl-auto의 위험성, Flyway와 Liquibase 비교, 환경별 마이그레이션 전략, 체크섬 오류 해결법을 정리한다.
분산 환경에서 채팅 메시지 순서를 보장하기 위해 서버 타임스탬프 + 클라이언트 정렬 + MongoDB 정렬 조회 전략을 설계한 과정을 정리한다.
UUID를 PK로 쓰면 안 되는 이유, Auto Increment의 한계, Snowflake ID로 내부 PK와 외부 노출용 ID를 분리한 과정을 정리한다.
C2C 공유 플랫폼 빌려조잉을 6주간 개발하며 채팅 시스템 설계, 팀원 이탈 대응, 그리고 삼성 우수상까지의 여정을 정리했습니다.
Spring의 여러 예외 처리 방법을 비교하고, @ControllerAdvice + 커스텀 에러 코드 + 통일된 응답 포맷을 선택한 이유를 정리한다.
채팅 메시지 브로커로 Kafka, RabbitMQ, Redis Stream, NATS를 비교하고, Redis Pub/Sub + MongoDB 조합을 선택한 이유를 정리한다.
properties와 yml의 차이, Spring Boot가 properties를 기본으로 생성하는 이유, 멀티모듈 프로젝트에서 yml을 선택한 배경을 정리한다.
채팅 시스템에서 MySQL(관계 데이터), MongoDB(메시지 저장), Redis(실시간 전달+캐싱) 세 DB를 함께 사용한 Polyglot Persistence 설계를 정리한다.
멀티모듈 프로젝트에서 의존성 버전 관리 방법 5가지를 비교하고, Version Catalog + Spring BOM 하이브리드 방식을 선택한 이유를 정리한다.
JWT 저장 방식의 역사(LocalStorage → HttpOnly Cookie)를 분석하고, SameSite 쿠키 정책으로 로컬 개발이 막힌 문제를 Vite 프록시로 해결한 과정을 정리한다.
헥사고날 아키텍처를 왜 선택하지 않았는지, 도메인 기반 멀티모듈 + 레이어드 아키텍처로 결정한 이유를 정리한다.
Kotlin + Java + Lombok 환경에서 발생하는 빌드 순서 문제와 Enum 호환성 이슈를 분석하고, kotlin-plugin-lombok + io.freefair.lombok + delombok 조합으로 해결한 과정을 정리한다.
즉각적인 피드백과 성취 공유 욕구를 결합하여 집중을 게임처럼 만드는 앱, 타이미의 기획 의도와 프로젝트 소개.
서버 재시작할 때마다 알림이 50-100건씩 쏟아져서 정작 중요한 알림을 놓치고 있었다. for 절과 inhibit_rules로 노이즈를 90% 줄인 과정을 정리한다.
Exception 로그가 여러 줄로 분리되어 Grafana에서 스택트레이스 검색이 안 됐다. Log4j2 JSON 포맷 + Promtail JSON 파이프라인으로 해결한 과정을 정리한다.
userId 기반 파티셔닝 때문에 헤비 유저의 이벤트가 한 파티션에 몰리면서 Lag 편차가 10배까지 벌어졌다. uploadId 기반으로 바꿔서 해결한 과정을 정리한다.
동시 요청 제한 없이 AI 서버에 보내다가 하루 5-10회 GPU OOM이 터졌다. ThreadPool + Semaphore 이중 동시성 제어로 OOM 0회를 달성한 과정을 정리한다.
음성 기반 노래 추천 플랫폼 오락가락을 5주간 개발하며 Kafka 파이프라인, GPU OOM 방어, Prometheus+Grafana 모니터링을 구축한 이야기입니다.
파일 업로드 후 동기 처리로 5-30초 걸리던 구조를 Kafka 기반 비동기 파이프라인으로 바꿔 200ms 이내 응답 + DLQ 패턴으로 실패 복구까지 구현한 과정을 정리한다.
서버를 거치는 업로드 방식이 OOM과 이중 전송 문제를 일으킨다는 걸 파악하고, Presigned URL로 S3 직접 업로드 + EventBridge로 완료 감지하는 구조로 전환한 과정을 정리한다.
Webhook, API, Actuator가 각각 다른 인증 방식을 요구해서 3개의 SecurityFilterChain을 @Order로 분리하고, 경로별 독립적인 보안 정책을 적용한 과정을 정리한다.
장애를 SSH로 확인하던 구조를 Prometheus(메트릭) + Loki(로그) + Grafana(시각화) + Alertmanager(알림)로 자동화해서 Critical 장애 감지를 ~85초 이내로 줄인 과정을 정리한다.
에지 케이스 테스트로 발견한 게시글과 댓글 관련 버그 12가지의 원인 분석과 해결 과정을 정리한다.
단위 테스트 환경을 MySQL에서 H2 인메모리로 전환하여 테스트 속도를 45% 개선한 과정과 Spring Profile 분리 전략을 정리한다.
청각장애인을 위한 온라인 교육 플랫폼 EduMeet을 6주간 개발하며 배운 것들을 정리했습니다.
UUID vs Auto Increment PK 전략, S3 업로드 방식 비교, 이미지 처리 접근 방식을 분석하고 프로젝트에 적합한 전략을 선택한 과정을 정리한다.
Spring Data JPA의 Custom Repository 구현체를 레이어 이동할 때 발생한 쿼리 메서드 파싱 오류와 해결 과정을 정리한다.
JPA N+1 문제의 4가지 해결 방법(FetchJoin, EntityGraph, SUBSELECT, BatchSize)을 실측 비교하고 최적 전략을 선택한 과정을 정리한다.
JPA Lazy 로딩에서 발생한 LazyInitializationException의 원인과 @EntityGraph를 이용한 해결 과정을 정리한다.
JPA @OneToMany의 기본 동작으로 생성된 불필요한 중간 테이블을 mappedBy로 제거한 과정을 정리한다.
레이어드 아키텍처를 선택한 이유와 의존성 역전으로 헥사고날 아키텍처까지 진화시키는 과정을 정리한다.