반응형

전체 글 181

Docusaurus 기반 API 문서 사이트 구축기: GitHub Actions → Cloudflare Pages 자동 배포까지

본 포스팅은 실제 업무 과정에서 진행한 Docusaurus + Cloudflare Pages 자동 배포 파이프라인 구축 기록입니다. 날짜와 프로젝트명, 도메인 등은 일부 마스킹 처리하였으며, 문제 해결 과정에서 얻은 인사이트를 중심으로 서술합니다.1. 배경과 목표팀에서 Bruno(.bru) 파일로 관리하던 API 컬렉션을 Docusaurus 기반 문서 사이트로 변환하는 작업이 진행 중이었다. 기존에는 .bru 파일에 docs {} 블록을 수동으로 작성하고, 로컬에서 npm run generate && npm run build를 돌려 빌드하는 방식이었다.최종 목표는 다음과 같았다:main 브랜치에 .bru 파일이 푸시되면 문서가 자동으로 재생성 및 배포된다.사이드바와 제목이 한글 기준으로 일관되게 노출된다..

MySQL FLUSH HOSTS

상황MySQL MCP 연결 시도 중 아래 에러 발생:Host 'xxx.xxx.xxx.xxx' is blocked because of many connection errors;unblock with 'mysqladmin flush-hosts'원인MySQL은 특정 호스트에서 연결 오류가 max_connect_errors(기본값 100) 횟수 이상 누적되면 해당 IP를 자동 차단한다.FLUSH HOSTS 란?MySQL 내부의 host_cache(호스트별 연결 오류 카운터)를 초기화하는 명령어.FLUSH HOSTS;또는 커맨드라인에서:mysqladmin flush-hosts -h -u -p위험성거의 없다.실제 데이터에 영향 없음현재 활성 연결 유지됨host_cache 재구성으로 인한 아주 미미한 일시적 오버..

Django + Lambda 환경에서 매치 리스트 API 쿼리 최적화 삽질기

오늘은 관리자용 리스트 API의 성능을 개선하면서 배운 점을 정리한다. 결론부터 말하면, 캐시보다 먼저 봐야 할 것은 실행 계획이었다.아래 내용은 외부 공개를 위해 도메인명, 수치, 일부 구현 세부를 일반화했다.배경운영 중인 관리자용 API에서 응답 시간이 간헐적으로 크게 튀는 문제가 있었다.APM을 보면 패턴은 크게 두 가지였다.패턴특징원인A애플리케이션 로직은 짧은데 전체 응답만 길다런타임 초기화 비용B요청 시간 대부분이 DB 구간에서 소비된다SQL 병목느린 패턴에서 특히 눈에 띈 쿼리는 특정 기간 데이터의 최소/최대 일시를 구하는 집계 쿼리였다.첫 번째 시도: ORDER BY LIMIT 1처음에는 aggregate보다 ORDER BY ... LIMIT 1이 인덱스를 더 잘 탈 것이라고 생각했다.# B..

카테고리 없음 2026.04.10

ElastiCache Redis Read/Write Splitting 적용기 - 장애에서 배운 이중화 전략

실제 프로덕션 Redis 장애를 겪고, 읽기/쓰기 분리를 적용하면서 배운 것들을 정리합니다.배경: Redis 장애가 터졌다어느 날 프로덕션 서비스가 전체적으로 느려졌다. API 응답 시간이 수십 배로 치솟고, Sentry에는 Redis timeout 에러가 쏟아졌다. 원인은 AWS ElastiCache t-type 인스턴스의 CPU Credit 소진이었다.t-type 인스턴스는 CPU Credit 기반으로 동작한다. 평소에는 Credit을 적립하고, 트래픽이 몰리면 Credit을 소모하면서 버스트 성능을 제공한다. 그런데 이날은 트래픽 급증으로 Credit이 바닥나버렸고, CPU 성능이 baseline(5~40%)으로 제한되면서 Redis가 극도로 느려진 것이다.Automatic Failover는 왜 동..

ElastiCache Redis 읽기/쓰기 분리 (Primary / Replica)

배경운영 중인 Django 백엔드의 Redis 캐시가 ElastiCache 클러스터(Primary 1 + Replica 2)로 구성되어 있었지만, 설정이 Primary Endpoint 하나만 가리키고 있어서 모든 읽기/쓰기 트래픽이 Primary 노드에 집중되고 있었다. ElastiCache가 제공하는 Reader Endpoint를 활용해 읽기는 Replica로, 쓰기는 Primary로 분산하는 작업을 했다.1. ElastiCache 엔드포인트 종류ElastiCache Redis 클러스터를 생성하면 세 종류의 엔드포인트가 제공된다.엔드포인트연결 대상용도Primary EndpointPrimary 노드 고정읽기 + 쓰기Reader Endpoint (-ro)Replica 노드들 (라운드로빈)읽기 전용개별 노드..

Django DRF에서 N+1 쿼리를 잡은 날 — Datadog에서 Prefetch까지

TLI (Today I Learned) · 2026-02-20태그: django drf orm n+1 prefetch datadog오늘의 작업/api/v2/stadium-groups/{id}/ API에서 Datadog APM을 통해 N+1 쿼리를 발견했고,Prefetch로 해결했다. 브랜치 생성 → 코드 수정 → 테스트 작성 → PR까지 한 사이클을 돌았다.문제 발견 — Datadog APM최근 배포 직후 Datadog에서 특정 API 트레이스를 확인했다.trace_id: 1981294581012217384span: stadium_image 쿼리 × 14회14개의 stadium_image SELECT가 개별로 날아가고 있었다.로직을 보면 즉시 이해가 됐다.AS-IS 코드 — 왜 N+1이 생겼나# Stad..

AWS Lambda 환경에서 S3 Presigned URL이 만료되는 문제 해결기

문제 발견어드민 페이지에서 매니저가 제출한 증빙서류 이미지가 엑박으로 표시되는 버그가 보고되었다. 일부 이미지는 정상적으로 보이는데, 특정 이미지들만 엑박이 뜨는 상황이었다.엑박이 뜨는 이미지의 URL로 직접 접속해보니 S3에서 다음과 같은 에러를 반환하고 있었다. ExpiredToken The provided token has expired.시스템 구조프로젝트는 Django 기반 REST API 서버로, AWS Lambda + API Gateway 환경에서 운영되고 있다. 이미지 파일은 S3에 저장되며, 조회 시 Presigned URL을 생성하여 응답한다.[클라이언트] → [API Gateway] → [Lambda (Django)] → [S3 Presigned URL 생성] ..

Obsidian Claudian 플러그인 설치 및 트러블슈팅 가이드

요즘 나의 제 2의 뇌를 만들기 위해 하루 일과나 있었던 일들 그리고 나의 감정들을 옵시디언에 기록하고 있다. 너무 재밌음.나중에는 이 기록들을 내가 자연어로 물어보기만 하면 답변을 바로 받을 수 있게끔 하기 위해서 클로디안 이라는 플러그인을 찾아냈다.이게 뭐냐면..바로 그냥 옵시디언 앱에서 클로드를 활용할 수 있다는 플러그인이었다.이번 포스팅에서는 내가 직접 Obsidian에서 Claude Code를 사이드바 채팅으로 사용할 수 있는 Claudian 플러그인을 설치하면서 겪은 문제들과 해결 과정을 정리했다.환경macOS (Apple Silicon)Obsidian 최신 안정 버전Claude Code 2.1.39 (Native Binary)Claudian 1.3.63Claudian이란?Claudian은 C..

Claude Code에서 MySQL MCP 서버 SSL 연결 문제 해결하기

Claude Code를 사용하다 보면 MCP(Model Context Protocol) 서버를 통해 다양한 외부 서비스와 연동할 수 있다. 그중에서도 MySQL 데이터베이스 연결은 개발 작업에 꽤 유용한데, 최근 AWS RDS MySQL에 연결하려다 꽤나 고생했던 경험을 공유하려고 한다.문제 상황어느 날 claude mcp list 명령어를 실행했더니 MySQL MCP 서버만 연결에 실패하고 있었다.$ claude mcp listcontext7: https://mcp.context7.com/mcp (HTTP) - ✓ Connectedsequential-thinking: npx @modelcontextprotocol/server-sequential-thinking - ✓ Connectedmysql: npx..

Django ORM aggregate Sum이 NULL을 반환할 때

오늘 코드 리뷰 받다가 알게 된 내용 정리.발단프라임타임 점수 계산 로직에서 이렇게 썼었다:def calculate_score_for_stadium(self, stadium_id: int) -> int: result = ( FixedMatch.objects.prime_time_weekday() .for_stadium(stadium_id) .aggregate(total_score=Sum(self._build_score_case_expression())) ) return result["total_score"] or 0 # _build_score_case_expression()에서 이미 default=Value(0)을 설정했는데, or 0이 필요한지 의문..

반응형