정규화와 비정규화, 어디까지 나눌 것인가
·
IT
데이터베이스 설계를 배울 때는 정규화를 하라고 배운다. 그런데 실무에서 성능 문제를 만나면 "비정규화하라"는 조언을 듣는다. 둘은 반대 방향인데 어느 쪽이 맞는가. 이 글은 정규화가 무엇을 막아주는지, 그리고 그것을 포기해도 되는 조건이 무엇인지를 정리한다.정규화가 막는 것은 모순이다정규화의 목적은 저장 공간 절약이 아니다. 같은 사실이 여러 곳에 적히는 것을 막는 것이다.주문 테이블에 고객 이름을 직접 넣었다고 하자. 고객이 이름을 바꾸면 그 고객의 모든 주문 행을 고쳐야 한다. 하나라도 빠뜨리면 같은 고객인데 이름이 다른 데이터가 생긴다. 어느 쪽이 맞는지 알 수 없다.이걸 이상 현상(anomaly)이라고 부르고 셋으로 나눈다.갱신 이상. 위 경우처럼 일부만 고쳐져 모순이 생긴다.삽입 이상. 아직 주..
HTTP 상태 코드와 오류 응답 설계
·
IT
API를 만들 때 오류가 나면 무조건 500을 주거나, 반대로 무조건 200에 {"success": false}를 담아 보내는 경우가 있다. 둘 다 나중에 문제가 된다. 이 글은 HTTP 상태 코드를 실제 상황에 어떻게 대응시킬지와, 오류 응답 본문을 어떻게 설계할지를 정리한다.200에 실패를 담으면 안 되는 이유먼저 가장 흔한 안티패턴부터. 모든 응답을 200으로 주고 본문에 성공 여부를 담는 방식이다.HTTP/1.1 200 OK{"success": false, "message": "권한이 없습니다"}동작은 한다. 문제는 HTTP 계층의 도구들이 전부 무력해진다는 것이다.로드 밸런서와 모니터링은 상태 코드로 오류율을 센다. 전부 200이면 오류율이 0%로 보인다. 실제로는 절반이 실패하고 있어도 대시보..
알림 피로를 줄이는 SLO 기반 모니터링
·
IT
알림이 하루에 수십 건 오는 팀이 있다. 처음에는 다들 확인하지만 곧 무시하게 된다. 그러다 진짜 장애 알림이 그 사이에 묻힌다. 이 글은 무엇을 알림으로 만들어야 하는지, 그리고 SLO를 기준으로 알림을 정리하는 방법을 다룬다. 결론부터 말하면 사람이 지금 행동해야 하는 것만 알림이어야 한다.알림 피로가 생기는 구조알림이 많아지는 이유는 대개 선의에서 시작된다. 장애를 놓치고 싶지 않으니 이것저것 걸어둔다. CPU 80%, 메모리 70%, 디스크 75%, 에러 로그 발생, 응답 시간 1초 초과.문제는 이 지표들이 사용자 경험과 직접 연결되지 않는다는 점이다. CPU가 80%여도 서비스는 멀쩡할 수 있다. 반대로 CPU가 30%인데 데이터베이스 커넥션이 고갈돼 모든 요청이 실패할 수도 있다.그래서 알림이..
환경 변수와 시크릿 관리, 어디까지 해야 하나
·
IT
데이터베이스 비밀번호를 어디에 두는지 물어보면 답이 제각각이다. .env 파일, 도커 이미지 안, 배포 스크립트, 심지어 저장소 안에 커밋된 설정 파일까지 나온다. 이 글은 시크릿을 다루는 단계별 기준과, 사고가 났을 때 무엇을 해야 하는지를 정리한다.설정과 시크릿은 다르다먼저 구분이 필요하다. 둘을 같이 다루면 규칙이 흐려진다.설정은 환경마다 다른 값이지만 노출돼도 피해가 없는 것이다. API 기본 URL, 로그 레벨, 타임아웃, 기능 플래그 같은 것들이다. 저장소에 넣어도 된다.시크릿은 노출되면 피해가 생기는 것이다. 데이터베이스 비밀번호, API 키, 서명 키, 인증서 개인키, OAuth 클라이언트 시크릿.기준은 단순하다. 이 값을 슬랙 공개 채널에 붙여넣어도 괜찮은가. 안 된다면 시크릿이다.절대 ..
메시지 큐를 언제 써야 하고 언제 쓰면 안 되나
·
IT
사용자가 회원가입 버튼을 눌렀는데 응답이 3초 걸린다. 확인해 보니 이메일 발송, 환영 알림, 추천 데이터 생성, 외부 CRM 등록을 모두 기다리고 있었다. 이럴 때 나오는 답이 메시지 큐다. 그런데 큐를 도입하면 시스템이 복잡해지고 새로운 실패 방식이 생긴다. 이 글은 큐가 실제로 푸는 문제와, 도입 전에 알아야 할 대가를 정리한다.큐가 푸는 세 가지 문제큐를 쓰는 이유는 대개 셋 중 하나다.응답 시간 분리. 사용자가 기다릴 필요 없는 작업을 나중으로 미룬다. 회원가입은 계정만 만들고 응답한다. 이메일은 큐에 넣고 워커가 처리한다.부하 평탄화. 순간적으로 몰리는 요청을 큐가 받아두고 워커가 감당 가능한 속도로 처리한다. 이벤트 시작 시각에 트래픽이 열 배로 뛰어도 데이터베이스는 평소 속도로 처리한다.결..
트랜잭션 격리 수준과 데드락 줄이기
·
IT
동시에 들어온 요청 두 개가 같은 데이터를 건드리면 무슨 일이 일어나는가. 재고가 1개 남았는데 두 명이 동시에 주문하면 어떻게 되는가. 이 질문의 답은 트랜잭션 격리 수준에 달려 있다. 이 글은 격리 수준이 실제로 무엇을 막아주고 무엇은 안 막아주는지, 그리고 데드락을 줄이는 방법을 정리한다.격리 수준이 막는 세 가지 현상SQL 표준은 동시성에서 생길 수 있는 문제를 세 가지로 정의한다.더티 리드(Dirty Read). 아직 커밋되지 않은 다른 트랜잭션의 변경을 읽는 것. 그 트랜잭션이 롤백되면 존재한 적 없는 값을 읽은 셈이 된다.반복 불가능 읽기(Non-repeatable Read). 같은 행을 두 번 읽었는데 값이 다른 것. 사이에 다른 트랜잭션이 커밋했기 때문이다.팬텀 리드(Phantom Rea..
API 버저닝, 언제 올리고 언제 안 올리나
·
IT
API를 만들고 나면 언젠가 바꿔야 할 때가 온다. 필드 이름을 고치거나, 응답 구조를 바꾸거나, 없애야 할 엔드포인트가 생긴다. 그런데 이미 쓰고 있는 클라이언트가 있으면 마음대로 바꿀 수 없다. 이 글은 버저닝 방식의 선택지와, 그보다 먼저 고민해야 할 "버전을 올리지 않고 바꾸는 법"을 다룬다.깨는 변경과 깨지 않는 변경먼저 구분이 필요하다. 모든 변경이 새 버전을 요구하지는 않는다.깨지 않는 변경(추가) 은 대체로 안전하다. 새 엔드포인트 추가, 응답에 새 필드 추가, 선택적 요청 파라미터 추가가 여기 속한다. 기존 클라이언트는 모르는 필드를 무시하면 되므로 영향이 없다.깨는 변경은 이런 것들이다. 필드 삭제나 이름 변경, 타입 변경(문자열 → 숫자), 필수 파라미터 추가, 기본값 변경, 오류 코..
타임아웃과 재시도, 서킷 브레이커까지
·
IT
외부 API를 호출하는 코드에 타임아웃을 안 걸어두면, 상대가 느려지는 순간 내 서비스가 함께 멈춘다. 재시도를 잘못 넣으면 이미 힘든 상대를 더 때려서 장애를 키운다. 이 글은 타임아웃, 재시도, 서킷 브레이커 세 가지를 어떤 순서로 어떻게 설정해야 하는지 정리한다.타임아웃이 없으면 스레드가 말라붙는다가장 먼저 할 일은 타임아웃 설정이다. 이게 없으면 나머지는 의미가 없다.외부 API가 응답하지 않으면 내 서버의 요청 처리 스레드(또는 커넥션)가 그 자리에서 기다린다. 요청이 계속 들어오면 대기하는 스레드가 쌓이고, 결국 풀이 고갈된다. 그러면 그 외부 API와 무관한 요청까지 처리하지 못한다. 장애가 전파되는 전형적인 경로다.타임아웃은 두 종류를 따로 잡아야 한다. 연결 타임아웃은 TCP 연결을 맺는..
헬스체크와 무중단 배포, 프로브 제대로 쓰기
·
IT
서비스를 배포할 때 진행 중이던 요청이 끊기거나, 새로 뜬 인스턴스에 트래픽이 너무 일찍 들어와 오류가 나는 경우가 있다. 둘 다 헬스체크와 종료 처리를 제대로 하지 않아서 생긴다. 이 글은 무중단 배포를 실제로 무중단으로 만드는 두 가지 장치를 다룬다.살아 있는 것과 받을 준비가 된 것은 다르다쿠버네티스가 프로브를 셋으로 나눈 이유가 여기 있다.Liveness(활성)는 "이 프로세스가 살아 있나"를 본다. 실패하면 컨테이너를 재시작한다. 데드락에 빠져 응답하지 못하는 상태를 복구하기 위한 것이다.Readiness(준비)는 "지금 트래픽을 받아도 되나"를 본다. 실패하면 재시작하지 않고 서비스 엔드포인트에서 뺀다. 의존하는 데이터베이스가 잠시 끊겼을 때, 재시작하는 대신 트래픽만 빼는 것이 맞다.Star..
페이지네이션, 오프셋 대신 커서를 쓸 때
·
IT
목록 API에 ?page=1&size=20을 붙이는 것은 익숙한 방식이다. 그런데 데이터가 쌓이면 뒤쪽 페이지가 느려지고, 스크롤하는 동안 같은 항목이 두 번 보이거나 어떤 항목은 아예 건너뛰어진다. 이 글은 오프셋 방식의 두 가지 구조적 문제와, 커서 방식이 그걸 어떻게 푸는지를 다룬다.오프셋은 건너뛴 만큼 읽는다LIMIT 20 OFFSET 100000이 어떻게 동작하는지 알면 문제가 보인다. 데이터베이스는 10만 개를 건너뛰기 위해 실제로 읽는다. 읽고 버린다. 그리고 그다음 20개를 돌려준다.SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 100000;그래서 페이지 번호가 커질수록 느려진다. 1페이지는 빠르고 5000페이지는 느리다. 인덱..
CORS 오류가 나는 진짜 이유와 해결법
·
IT
프런트엔드에서 API를 호출했는데 콘솔에 CORS 오류가 뜬다. 서버 개발자에게 물어보면 "서버에서는 잘 되는데요"라고 한다. 둘 다 맞는 말이다. CORS는 브라우저가 거는 제약이지 서버의 문제가 아니기 때문이다. 이 글은 CORS가 왜 존재하고 오류가 정확히 무엇을 뜻하는지, 그리고 상황별로 어떻게 푸는지를 정리한다.브라우저만 막는다먼저 이 사실부터 분명히 해야 한다. curl이나 서버 코드에서 같은 API를 호출하면 아무 문제 없이 된다. CORS는 브라우저가 자바스크립트로 다른 출처에 요청할 때 적용하는 규칙이다.왜 이런 제약이 있나. 브라우저는 쿠키를 자동으로 붙인다. 만약 제약이 없다면, 악성 사이트가 사용자가 로그인해 둔 은행 사이트에 요청을 보내고 응답을 읽을 수 있다. 쿠키가 자동으로 붙..
JWT와 세션, 무엇을 골라야 하나
·
IT
로그인을 구현할 때 세션과 JWT 중 무엇을 쓸지 정해야 한다. 검색해 보면 "JWT는 확장성이 좋다", "세션은 안전하다" 같은 요약이 나오는데, 둘 다 절반만 맞는 말이다. 이 글은 둘의 실제 차이와, 선택을 가르는 조건을 정리한다. 결론부터 말하면 로그아웃을 즉시 반영해야 한다면 세션 쪽이 훨씬 단순하다.근본적인 차이는 상태를 어디에 두는가세션 방식은 서버가 상태를 갖는다. 로그인하면 서버가 세션을 만들어 저장소에 넣고, 클라이언트에는 세션 ID만 쿠키로 준다. 요청이 올 때마다 그 ID로 저장소를 조회한다.JWT 방식은 서버가 상태를 갖지 않는다. 로그인하면 사용자 정보를 담고 서명한 토큰을 준다. 요청이 오면 서명만 검증하면 되고, 저장소 조회가 없다.여기서 모든 장단점이 파생된다.JWT 의 장..