DB 인덱스가 있어도 왜 안 탈까? EXPLAIN ANALYZE로 확인해본 옵티마이저 판단

DB 성능 문제를 볼 때 제일 먼저 떠오르는 게 인덱스다.쿼리가 느리다.그러면 일단 where 조건에 걸린 컬럼을 보고 “여기 인덱스 없나?”부터 확인하게 된다.그런데 막상 인덱스를 만들어도 쿼리가 빨라지지 않는 경우가 있다.처음에는 이게 좀 이상했다.분명히 인덱스가 있는데 왜 안 쓰지?DB가 못 찾는 건가?아니면 인덱스를 잘못 만든 건가?보다 보니 이유는 단순했다.DB는 인덱스가 있다고 무조건 쓰지 않는다.옵티마이저가 봤을 때 인덱스를 타는 것보다 테이블을 그냥 읽는 게 더 낫다고 판단하면 Seq Scan을 선택한다.PostgreSQL 공식 문서에서도 EXPLAIN은 쿼리 플래너가 어떤 실행 계획을 만들었는지 보여주는 명령이라고 설명한다.실행 계획에는 테이블을 순차 스캔할지, 인덱스 스캔할지 같은 정보..

DB 인덱스가 있어도 왜 안 탈까? EXPLAIN ANALYZE로 확인해본 옵티마이저 판단
Archive Backend/Dev

Go 포인터, 언제 써야 할까?

Go를 처음 쓸 때 포인터가 조금 헷갈렸다.C처럼 메모리를 직접 만지는 느낌까지는 아닌데, 막상 Go 코드를 보다 보면 *User, &user, *name 같은 코드가 계속 나온다.처음에는 단순하게 생각했다.“그냥 struct는 전부 포인터로 넘기면 되는 거 아닌가?”그런데 계속 쓰다 보니 그렇게만 볼 문제는 아니었다.Go에서 포인터를 이해하려면 먼저 문법보다 값이 복사되는 방식을 보는 게 더 편했다.Go는 기본적으로 값을 복사해서 넘긴다Go에서 함수에 값을 넘기면 기본적으로 복사된다.package mainimport "fmt"func changeName(name string) { name = "jian"}func main() { name := "go" changeName(name) fmt.Println..

Go 포인터, 언제 써야 할까?
Archive Backend/Dev

RabbitMQ를 이해해보자, 메시지 실패 처리와 Ack, DLQ 정리 (3)

RabbitMQ를 실제로 쓰다 보면 메시지를 보내는 것보다 더 중요한 게 있다.바로 실패 처리다.처음에는 Producer가 메시지를 넣고, Consumer가 가져가서 처리하면 끝이라고 생각했다.Producer ↓RabbitMQ ↓Consumer 그런데 실제 서비스에서는 Consumer가 항상 성공하지 않는다.DB 저장 중 에러가 날 수 있다.외부 API가 잠깐 죽어 있을 수 있다.메시지 포맷이 잘못됐을 수도 있다.Consumer가 처리 중간에 죽을 수도 있다.이때 메시지를 어떻게 할지가 중요하다.RabbitMQ에서 이 부분을 이해하려면 먼저 ack를 알아야 한다.Ack는 처리 완료 신호다Consumer가 Queue에서 메시지를 가져갔다고 해서 RabbitMQ가 바로 메시지를 지워버리면 위험하다.예를 ..

RabbitMQ를 이해해보자, 메시지 실패 처리와 Ack, DLQ 정리 (3)
Archive Infra

RabbitMQ를 이해해보자, Exchange와 Queue가 헷갈렸던 이유 (2)

RabbitMQ를 처음 공부할 때 제일 헷갈렸던 부분은 Exchange였다.처음에는 Producer가 Queue에 바로 메시지를 넣는다고 생각했다.Producer → Queue → Consumer 개념을 단순하게 볼 때는 이 그림이 편하다.그런데 RabbitMQ를 실제로 보면 중간에 Exchange가 있다.Producer → Exchange → Queue → Consumer 처음에는 이게 왜 필요한지 잘 와닿지 않았다.“그냥 Queue에 바로 넣으면 되는 거 아닌가?”이 생각이 먼저 들었다.그런데 메시지를 어디로 보낼지 나누기 시작하면 Exchange가 왜 있는지 조금씩 이해된다.Producer는 메시지를 발행한다.하지만 Producer가 모든 Queue를 직접 알고 있으면 구조가 복잡해진다.예를 들어 ..

RabbitMQ를 이해해보자, Exchange와 Queue가 헷갈렸던 이유 (2)
Archive Infra

RabbitMQ를 이해해보자, 메시지 큐를 왜 쓰는 걸까? (1)

RabbitMQ를 처음 봤을 때는 그냥 “메시지를 잠깐 쌓아두는 곳” 정도로 생각했다.API 서버에서 바로 처리하기 애매한 작업을 RabbitMQ에 넣어두고, Consumer가 나중에 가져가서 처리하는 구조.처음에는 이 정도만 이해해도 크게 문제는 없었다. 그런데 막상 실제 서비스 흐름에 끼워 넣으려고 하니까 생각보다 볼 게 많았다.단순히 큐에 넣고 빼는 문제가 아니었다.요청 처리 속도, 실패 처리, 재시도, 메시지 유실, 중복 처리 같은 것들이 같이 따라왔다.그래도 처음에는 큰 흐름부터 잡는 게 좋다.RabbitMQ는 메시지 브로커다.서비스와 서비스 사이에서 메시지를 받아두고, 필요한 Consumer에게 전달해주는 역할을 한다.예를 들어 사용자가 회원가입을 했다고 해보자.회원가입 요청이 들어오면 서버는..

RabbitMQ를 이해해보자, 메시지 큐를 왜 쓰는 걸까? (1)
Archive Infra

HAProxy로 무중단 배포를 이해해보기

처음 무중단 배포라는 말을 들었을 때는 조금 거창하게 느껴졌다.서비스를 끊지 않고 배포한다는 말은 쉬운데, 막상 생각해보면 애매한 부분이 많다.서버를 재시작해야 하는데 사용자는 계속 요청을 보내고 있고, 그 요청이 어느 서버로 가는지도 생각해야 한다.배포 중인 서버로 요청이 들어가면 에러가 날 수도 있고, 반대로 너무 빨리 서버를 내리면 처리 중이던 요청이 끊길 수도 있다.그래서 중간에 트래픽을 조절해주는 무언가가 필요하다.그 역할을 하는 대표적인 도구 중 하나가 HAProxy다. HAProxy는 TCP/HTTP 요청을 여러 서버로 나눠 보내는 로드밸런서이자 리버스 프록시로 많이 사용된다.공식 사이트에서도 HAProxy를 TCP와 HTTP 기반 애플리케이션을 위한 빠르고 안정적인 reverse proxy..

HAProxy로 무중단 배포를 이해해보기
Archive Infra

Go 명령어 정리

go mod initGo 프로젝트를 처음 만들 때 가장 먼저 쓰는 명령어다.go mod init github.com/username/project-name 이 명령어를 실행하면 go.mod 파일이 생긴다.처음에는 이 파일이 그냥 패키지 목록 정도라고 생각했는데, 실제로는 프로젝트의 모듈 이름과 Go 버전, 의존성 정보를 관리하는 기준 파일에 가깝다.예를 들어 Gin 프로젝트를 만든다면 보통 이런 식으로 시작한다.mkdir go-samplecd go-samplego mod init github.com/limjian/go-sample 여기서 모듈 이름은 나중에 내부 패키지를 import할 때도 영향을 준다.그래서 대충 test 이런 식으로 만들었다가 나중에 구조를 잡을 때 다시 고친 적도 있다.go get외..

Go 명령어 정리
Archive Backend/Dev

Uvicorn과 Gunicorn은 뭐가 다를까?

먼저 웹 프레임워크와 웹 서버는 다르다Django, Flask, FastAPI 같은 것은 웹 프레임워크다.프레임워크는 라우팅, 요청 처리, 응답 생성, 미들웨어, 인증 같은 애플리케이션 로직을 작성할 수 있게 도와준다.하지만 실제 운영 환경에서 외부 요청을 받고, 프로세스를 띄우고, 워커를 관리하고, 애플리케이션을 실행해주는 역할은 별도의 서버가 담당한다.예를 들어 이런 구조다.Client ↓Nginx ↓Gunicorn / Uvicorn ↓Django / Flask / FastAPI 여기서 Nginx는 보통 리버스 프록시 역할을 한다.정적 파일 처리, SSL 종료, 요청 전달, 로드밸런싱 같은 일을 맡는다.그리고 Gunicorn이나 Uvicorn은 Python 애플리케이션을 실제로 실행하는 서버 역..

Uvicorn과 Gunicorn은 뭐가 다를까?
Archive Backend/Dev

리눅스 netstat과 ss 차이

서버에서 포트가 열려 있는지 확인할 때 예전에는 netstat을 자주 썼다.나도 거의 습관처럼 아래 명령어를 썼다.netstat -tulnp 그런데 요즘 리눅스 서버에서는 netstat이 아예 설치되어 있지 않은 경우가 꽤 있다.netstat: command not found 처음 보면 순간 당황한다.포트 확인해야 하는데 왜 명령어가 없지 싶다.이때 대신 쓰는 명령어가 ss다. 역할은 거의 비슷하다.서버에서 열려 있는 포트, 연결된 TCP 세션, 어떤 프로세스가 포트를 사용 중인지 확인할 때 쓴다.netstat은 오래된 방식netstat은 예전부터 많이 쓰이던 네트워크 상태 확인 명령어다.보통 net-tools 패키지에 포함되어 있다.netstat -tulnp 옵션은 대략 이런 의미다.-t : TCP-..

리눅스 netstat과 ss 차이
Archive Infra

Python 패키지 관리 도구 uv를 써보면서 느낀 점

Django 6.0 릴리스 내용을 보다가 자연스럽게 Python 패키지 관리 쪽도 다시 보게 됐다.Django 6.0은 Python 지원 버전 기준도 올라갔고, 새 프로젝트를 만든다면 Python 버전이나 가상환경 관리 방식도 같이 봐야 했다.그러다 uv가 다시 눈에 들어왔다.사실 uv는 FastAPI로 간단한 프로젝트를 만들 때 한 번 써본 적이 있다.그때는 깊게 찾아보고 사용했다기보다는, 요즘 Python 쪽에서 자주 보이는 도구라 가볍게 적용해본 정도였다.그런데 문서를 조금 더 보니, uv는 단순히 pip install을 빠르게 해주는 도구라기보다는 Python 프로젝트를 시작하고 관리하는 흐름 자체를 정리하려는 도구에 가까웠다.패키지를 설치하고, 가상환경을 만들고, Python 버전을 관리하고, ..

Python 패키지 관리 도구 uv를 써보면서 느낀 점
Archive Backend/Dev

WSGI와 ASGI 차이 정리, Python 웹 서버 구조 이해하기

Python 웹 애플리케이션이 요청을 어떻게 받고, 서버와 어떻게 연결되고, 동기와 비동기 처리를 어떻게 다루는지 이해하려면 WSGI와 ASGI 차이를 한 번쯤 정리해두는 게 좋겠다는 생각이 들었다. WSGI란?WSGI는 Web Server Gateway Interface의 약자다.쉽게 말하면 Python 웹 애플리케이션과 웹 서버 사이를 연결해주는 표준 인터페이스다.예를 들어 Django나 Flask로 웹 애플리케이션을 만들었다고 해도, 실제로 사용자의 요청을 바로 받는 것은 보통 웹 서버나 애플리케이션 서버다.이때 웹 서버와 Python 애플리케이션이 서로 약속된 방식으로 요청과 응답을 주고받아야 한다.그 약속이 바로 WSGI다.Client ↓Nginx ↓Gunicorn / uWSGI ↓Djan..

WSGI와 ASGI 차이 정리, Python 웹 서버 구조 이해하기
Archive Backend/Dev

Redis Sentinel 정리, Failover와 Redis Cluster 차이 이해하기 (2)

이전 글에서는 Redis Sentinel이 왜 필요한지 정리했다.Redis Sentinel은 Redis Master와 Replica를 감시하고, Master에 장애가 발생했을 때 Replica를 새로운 Master로 승격하는 역할을 한다.이번 글에서는 조금 더 운영 관점에서 정리해보려고 한다.Redis Sentinel은 장애가 나면 실제로 어떤 흐름으로 동작할까?Redis Cluster와는 정확히 뭐가 다를까?Sentinel은 언제 쓰고, Cluster는 언제 써야 할까?Sentinel을 쓴다고 데이터가 100% 안전한 걸까?이 부분을 정리해두면 Redis 구조를 선택할 때 훨씬 덜 헷갈린다.Redis Sentinel 장애 복구 흐름먼저 Sentinel의 장애 복구 흐름을 간단히 보자.기본 구조는 이렇다..

Redis Sentinel 정리, Failover와 Redis Cluster 차이 이해하기 (2)
Archive Infra