서비스 초기 팀 여건 상 프론트엔드와 백엔드 모두 혼자 개발해야 했던 상황에서, 당시 가장 중요한 것은 개발 속도였다.
서비스 초기이기 때문에 트래픽이 많지 않을 것이라고 예상했고, Prisma 같은 ORM이 Python에 최적화되어 있지 않아도 서비스 초기의 트래픽을 견디는 데에는 아무 문제 없을 것이라고 판단했다.
더군다나 사전에 EKS 환경에서 HPA + Karpenter 등으로 Scalable 한 인프라를 구축해두었기 때문에 속도를 최우선해야 한다면 내가 개발하기 가장 편하고 프레임워크 상 종속이 적고 자유도가 높은 언어로 개발하는 것이 최선이라고 판단하였다.
나는 이미 사전에 FastAPI Base Project가 이미 마련되어있어서, 이 프로젝트를 기반으로 바로 개발을 시작하였고 빠르게 서비스를 만들어나갔다.
가장 중요한 것은 채팅 서버를 어떤 구조를 만들까 고민해야 했다.
당시 Featherless 에서 LLM을 Serving하고 있었다.
Featherless는 서버리스 기반 큐로 운영되고, 비용 구조가 Max Concurrent Request에 따라 고정 비용을 내고 넘치는 요청은 큐에서 대기하는 방식이었다.
단순히 채팅 API를 만든다면 Featherless에서 항상 터질 것이므로 그 전에 채팅방에서도 큐를 두어서 LLM Call이 터지지 않도록 관리해야 했다.
그렇게 생각한 이유는 채팅을 보냈을 때 Featherless에서 대기하게 된다면 캐릭터 응답이 날라오지 않는 거처럼 느끼는데, 채팅방에서 대기하는 것은 사용자 경험으로 전환시킬 수 있기 때문이다.
아래와 같이 Progress Bar와 함께 Waiting Queue에서 대기하도록 하면 조금 더 Responsive 하고, 배경화면 등 다른 요소로 덜 기다리는 것처럼 UX를 만들 수 있기 때문이다.
결론적으로는 Featherless 큐와는 별개로 우리 서비스 자체에서도 큐를 두어서 사용자 채팅방을 관리하기로 하였다.

우리는 당시 비회원과 회원 모두 채팅이 가능했고, 회원 중 구독자는 큐 없이 바로 채팅방에 들어가는 구조였다.
비회원은 채팅방 없이 캐릭터와 채팅을 하는 구조였기 때문에 인증이 필요 없어서 바로 큐로 들어가면 돼서 복잡한 구조가 아니었다.
회원은 이 채팅방이 유저의 것인지 검증하는 과정 등 여러 회원 검증 로직이 큐에 들어가기 전에 실행되어야 했다.
그리고 큐에서 대기하는 사용자는 현재 몇 번째 위치에 있는지 실시간으로 받아봐야 했다.
그래서 채팅방을 하나의 상태로 정의하고, 큐에서 대기하는 상태, 채팅방에서 채팅하는 상태를 하나의 연결로 정의한 뒤 언제든지 서버에서 정보를 실시간으로 받아볼 수 있도록 하기 위해 웹 소켓으로 서버를 구현하기로 결정하였다.
1. FastAPI Websocket Server
FastAPI + Websocket (Python)을 선택한 이유
1. 개발 공수가 최대한 적게 들어야 한다.
- 현재 모든 서버가 Python / FastAPI로 되어있기 때문에, Python 기반 프레임워크로 개발하는 것이 가장 합리적이라고 생각하였다.
2. 현재 큐에서 대기하고 있는지, 내가 몇 번째 위치에 있는지 실시간으로 업데이트 되어야 한다.
- 큐에서의 위치가 계속 바뀌어야 사용자가 Responsive 한 느낌이 들 것이라고 생각했다.
3. 채팅은 한 번에 나오는 게 아니라 토큰 별로 나오기 때문에 클라이언트는 짧은 주기로 응답을 받을 수 있어야 한다.
- 토큰은 1초에 8-10번 수준으로 나오기 때문에, 문제 없이 초당 10번 정도의 응답을 무리 없이 받을 수 있어야 한다.
메인 서버 구조
그리고 인증 / 큐 / 채팅 등의 기능은 Main Socket Server에 있지 않고, MSA를 채택하여 각 기능을 하는 서버에 요청을 날리는 방식이다.
Main Socket Server에는 로직이 많지 않고, 채팅방 입장부터 채팅 마무리까지의 파이프라인을 외부 API와 소통하며 순차적으로 처리하는 구조였다.

[웹소켓 연결 시작]
// [Request]
{
"request_id": "uuid from front"
"token": "jwt token",
"chatroom_id": "object id",
"type": "session_start"
}
// [Response]
{
"status": "CONNECTED",
"data": {
"metadata": {
"is_authenticated": true,
"is_queue_bypassed": false,
// ...
},
"chatroom": {
// ...
},
"user": {
// ...
}.
// ...
}
}[큐에 있을 때] – 0.5초마다 주기적으로 상태를 보내줌
{
"status": "WAITING",
"data": {
"total": 65,
"position": 14
}
}- 매번 Queue Server로 요청을 보내지 않고, Main Server 단에서 메모리에 상태를 캐싱한다.
[채팅 응답]
// [Request]
{
"request_id": "uuid from front"
"message": "*with smile* I wa...",
"regenerated_chat_id": null,
"type": "chat"
}
// [Response]
{
"status": "CHAT",
"data": {
"contentType": "text",
"message": "*with smile* I re..."
}
}다행히 생각한대로 채팅 전체 로직이 잘 동작해주어서 이틀 정도 걸려 채팅 웹소켓 서버를 구현할 수 있었다.
- 웹소켓 서버 구현보다 프론트에 Integration 하는 작업이 더 걸린 거 같다.
결과적으로는 서비스 개발 시작부터 배포까지 약 두 달 정도 걸려서 매우 빠르게 배포할 수 있었다.
- 약 20개 정도의 프론트엔드 페이지 (React)
- 7개의 Micro Services (FastAPI)
- Auth Server
- Main Backend Server
- Main Socket Server
- Queue Server
- Inference Server
- Embedding Server
- VDB/RAG Server
(Embedding Server, VDB/RAG Server는 다행히 이전 Product에 쓰던 서버를 그대로 재사용할 수 있었다.)
서비스 운영 후 1달 정도가 지나자 사용자가 서서히 늘어 큐에는 항상 몇 십 명의 사용자가 대기하게 되었다.
서비스의 메인 타겟이 북미이기 때문에 우리가 자는 시간대에 사용자들이 많이 사용하였다.
그러다보니 새벽 3-4시에 디스코드 채널에서 자꾸 아래와 같은 이슈들이 보고되었다.
Chats are not working...Waiting line number is not shown...
로그를 보니 몇몇 사용자는 큐에 들어가기에도 전에 연결이 끊기거나, 몇몇은 채팅 중 갑자기 소켓 연결이 끊기는 현상이 있었다.
배포 전 몇 번이나 채팅이 잘 되는지 여러 번 QA를 거치고 배포 했는데도 이런 현상이 발생하니 원인이 무엇인지 특정하기 굉장히 어려웠다.
그래서 나는 로그만으로는 원인을 파악하기 어렵다고 생각하여 같은 현상을 재현해보려고 하였다.
연결이 끊기고 채팅이 되지 않는 현상은 트래픽이 급격히 늘어난 후에 발생했다고 파악하고 부하 테스트를 진행하였다.
- 부하 테스트 결과를 디스코드 웹훅으로 보내서 팀원들에게도 이 결과를 빠르게 공유하고, 한 번에 정리해서 여러 번 실험을 할 수 있도록 하였다.

결과는 다소 충격적이었다.
요청 초기에는 모든 요청이 성공하다가 갑자기 인증 사용자에서 실패 비율이 폭주한 것이다.
인증된 사용자의 기준 성공률이 15% 밖에 안 되어서 이건 큰 문제가 있다고 판단하고 핫픽스를 준비하기 시작했다.
- 정확한 원인을 특정하지는 못했지만 같은 현상을 재현할 수 있어서 매우 다행이라고 생각했다.
인증된 사용자와 인증 안 된 사용자의 로직 차이는 큐에 들어가기 전 인증 밖에 없으므로 인증 서버가 불안해서 그런가 라는 의문에 API Call 로직을 없애고 Direct Database Call로 바꾼 뒤 테스트를 해보았는데, 똑같은 결과가 나왔다.
문제는 인증 서버가 아니라, 인증 로직 자체가 WebSocket 서버에 추가되는 순간 발생하고 있었다.
이를 검증하기 위해 세션을 저장하는 클래스에 로깅을 걸어보니 로드 테스트 중 실제 유저와 생성된 세션의 개수 그래프를 그려보었더니 인증 과정에서 생기는 세션이 클래스에 담기지 않는 다는 것을 확인하고, 인증 과정에서 문제가 생겨 그 다음으로 진행하지 못 한다는 것을 확인했다.

원인 분석
사용자가 적을 때는 문제 없이 동작하는 것을 봐서 로직 자체에는 문제가 없다고 판단했다.
트래픽이 많아졌을 때만 문제가 있으므로, 인증 로직을 거칠 때 언어나 프레임워크 단에서 놓치고 있는 부분이 있다고 생각했다.
FastAPI의 WebSocket 서버는 내부적으로 ASGI + asyncio event loop 기반으로 동작한다.
하나의 프로세스는 하나의 event loop를 가지고, 모든 WebSocket 연결은 이 event loop 위에서 실행된다.
웹소켓 연결 특성 상 long-lived connection이기 때문에 WebSocket 핸들러는 한 번 연결되면 오래 살아 있는 작업이 된다.
또한 대기 중인 사용자에게 0.5초마다 Waiting 상태를 push 하는 구조는 Python WebSocket 서버에서 event loop를 지속적으로 잠식한다.
게다가 웹소켓 연결을 유지하기 위해 채팅방에 들어간 사용자로부터 Keep Alive를 주기적으로 보내도록 설정하기도 하였고, 채팅을 치는 시간, 채팅을 읽는 시간 등 직접 소통이 없는 타이밍에도 세션 연결은 계속 유지되어 있어야 한다는 것이다.
트래픽이 몰리는 순간에 인증된 사용자들의 WebSocket 연결 요청이 동시에 들어오면서 event loop는 인증 로직을 처리하느라 바빠진다.
그래서 새로운 WebSocket 연결을 accept 하지 못하고 대기시킨다. 일부 요청은 타임아웃으로 연결이 끊긴다.
그리고 로드 테스트에서는 첫 메시지를 받으면 테스트 성공이라고 가정을 했기 때문에
인증 유저도 인증 과정에서 실패하는 결과가 명시적으로 보였을 뿐 사실은 비회원 유저도 만약 계속 채팅을 진행했으면 실패했을 것이라고 추측하였다.
이외로 우리는 uvicorn worker 수 증가, 인증 로직 캐싱, 대기 업데이트 주기 증가 같은 해결책도 모두 반영해보았는데 모두 큰 도움을 주진 못했다.
내가 최종적으로 내린 해결책은 언어 자체를 빠르게 바꾸는 것이었다.
우선 MSA 환경에서 다른 API를 Call 할 때 생긴 문제가 아니었기 때문에 현재 구조를 그대로 가져가도 문제가 없다고 생각했고, 자체 로직 없이 모두 API를 Call 하는 Wrapper 느낌으로 서버가 구성되어있었기 때문에 웹소켓을 효율적으로 돌릴 수 있는 구조의 언어라면 뭐든 상관 없을 것이라고 생각하였다.
고려했던 프레임워크는 Express(Typescript, node.js 런타임)랑 Gorilla(Go)이었다.
둘 다 WebSocket을 안정적으로 지원하고, 리소스도 많이 먹지 않으면서 가볍게 병렬 프로그래밍을 할 수 있도록 지원해주기 때문에 현재 구조처럼 로직 없이 API를 호출하고 중계하는 서버를 구현하는 데에는 어떤 선택지를 골라도 될 것이라고 생각하였다.
[러닝 커브]
Typescript와 Go는 둘다 이미 사용해본 언어였기 때문에 어떤 언어/프레임워크를 선택하든 개발 공수는 크게 다르지 않을 것이라고 판단했다.
또한 메인 서버의 구조는 프레임워크에 강하게 의존하지 않고, 각 단계가 명확한 파이프라인 기반 처리 구조이기 때문에 두 언어 모두 추가 학습이 많이 필요하지 않았다.
Node.js 진영의 가장 큰 장점 중 하나는 큰 라이브러리 생태계인데, 내가 구현해야 할 서버는 WebSocket 처리와 기본적인 HTTP 요청 처리 정도였기 때문에 풍부한 라이브러리 생태계가 선택을 결정지을 만큼의 요소는 아니었다.
따라서 러닝 커브 관점에서는 Go든 Node.js든 큰 차이는 없었다.
[성능]
성능은 두 프레임워크 모두 좋다고 알고 있어서 결정적인 요소는 아니었지만, 여러 벤치마크와 분석 글을 참고하면서 자연스럽게 비교하게 되었다.
Go는 리소스 사용량이 적고 런타임 오버헤드가 상대적으로 낮으며 고루틴(Goroutine)을 통해 병렬 처리를 매우 가볍게 할 수 있다.
특히 서버가 동시 연결을 많이 처리해야 하는 WebSocket 중심 구조이기 때문에 Go의 동시성 모델은 분명 장점으로 느껴졌다.
Python과 비교하면 Node.js와 Go 모두 훨씬 좋은 선택이고, 둘 사이에서 체감 성능 차이가 아주 클 것이라고 보지는 않았다.
그래서 성능은 Go를 선택해도 충분히 좋은 이유정도였지, Go를 선택해야만 하는 이유는 아니었다.
아래는 내가 공부 참고한 여러 분석글 중 일부이다.
[스타일]
Go를 선택한 가장 큰 이유는 개발 스타일이 내 코딩 스타일과 서비스 구조와 잘 맞았기 때문이다.
채팅 메인 서버는 병렬 처리와 백그라운드 작업이 많았고, WebSocket 메시지를 단계별로 처리하는 파이프라인 구조이다.
따라서 각 단계에서 명확한 입력 검증과 쉬운 병렬 처리를 지원해야 쉽게 개발할 수 있을 거 같았다.
Go는 아래 한 줄로
- 이 로직은 메인 흐름과 분리해서 병렬로 실행한다
는 의도가 명확히 드러난다.
go backgroundTask()물론 Node.js 진영에서도 같은 동작을 구현할 순 있지만, Goroutine 기반의 병렬처리가 내게 더 직관적으로 다가왔다.
그래서 실제로 코드를 읽을 때나 구조를 설계할 때 생각의 부담이 굉장히 적었다.
그리고 채널(Channel)을 통해 고루틴 ↔ 메인 플로우 간 데이터 전달도 가능했고 이벤트 흐름 제어도 쉽게 할 수 있었기에 내가 원하는 서버 동작을 Python에서 구현되어 있던 코드 구조 그대로 어렵지 않게 옮길 수 있었다.
다음으로 Go는 try/catch를 지원하지 않는다. 대신 타입에 굉장히 엄격하고, 암묵적 형변환을 허용하지 않는다.
메인 웹소켓 서버는 파이프라인 기반 처리이기 때문에 각 단계에서 입력을 검증하고 잘못된 경우 즉시 종료하거나 기본 값을 명시적으로 설정하는 흐름이 중요했다.
personaId, ok := data["persona_id"].(string)
if !ok {
log.Printf("failed to parsing persona id")
return
}
characterId, ok := data["character_id"].(string)
if !ok {
log.Printf("failed to parsing character id")
return
}
message, ok := data["message"].(string)
if !ok {
message = ""
}
contentType, ok := data["content_type"].(string)
if !ok {
contentType = "text"
}이 코드를 보면 어떤 필드가 필수인지 한눈에 보이고 어디서 종료되는지가 명확하며 이후 단계에서는 이미 검증된 값만 다룬다는 전제가 성립한다.
적어도 채팅 웹소켓 서버에서는 try/catch로 에러를 처리하는 방식보다 각 단계에서 책임을 명확히 끊어내는 구조가 더 잘 어울린다고 생각했다.
러닝 커브
• 둘 다 알고 있었기 때문에 고려 대상이 아니었음
성능
• 둘 다 알고 있었기 때문에 고려 대상이 아니었음
스타일
• Go의 파이프라인 + 동시성 처리가 내가 보기에 더 직관적이었다
빠르게 개발해야 하는 상황이었기 때문에 솔직하게 만약 Go를 전혀 몰랐으면 Node.js(Express)를 선택했을 거 같다.
“Go 좋다고 하니깐 써봐야지” 라는 근거 없는 선택이 아니라, 지금 만들고 있는 서버의 구조와 내가 선호하는 사고 방식을 종합적으로 고려해서 나온 선택이었다.
그리고 개발 일정이 매우 타이트했기 때문에, 새로운 언어나 프레임워크를 공부하면서 시행착오를 겪을 여유 자체가 없었다.
그렇기 때문에 한정된 시간 안에 가장 자신 있고, 안정적으로 구현할 수 있는 선택을 하였다.
- 매일 밤 새벽에 채팅이 안 된다는 리포팅이 올라오는 것을 상상해보면, 늦어도 이틀 안에는 해결해야 하는 문제다.
2. Go (gorilla) Websocket Server
Go로 채팅 웹소켓 서버를 개발하기로 결정하고, 바로 개발에 들어갔다.
Python -> Go 로 완전히 옮기는데 5시간 정도 걸렸다.
폴더 구조는 아래와 같다.
.
├── cmd/
│ └── server/
│ └── main.go -> 웹소켓 엔드포인트
│
├── internal/
│ ├── chain/
│ │ └── chain.go -> 채팅 파이프라인
│ │
│ ├── manager/
│ │ └── queue_manager.go -> Queue 상태 로컬 캐시
│ │
│ ├── model/
│ │ ├── request_model.go -> Request DTO 역할
│ │ └── response_model.go -> Response DTO 역할
│ │
│ ├── service/
│ │ ├── authentication_service.go -> 인증 Micro Service
│ │ ├── backend_service.go -> 백엔드 Micro Service
│ │ ├── inference_service.go-> 채팅 Micro Service
│ │ ├── queue_service.go-> 큐 Micro Service
│ │ └── var.go
│ │
│ ├── telemetry/ -> Telemetry 로깅
│ │ ├── setup.go
│ │ ├── trace.go
│ │ └── websocket.go
│ │
│ └── util/
│ └── http_request.go -> Service에서 사용하는 http util구조는 매우 간단하다.main.go에서 chain.go에 정의돼 있는 chatPipeline(...)을 호출하면 끝이다.
chain.go에서는 service 폴더 내부에 있는 여러 Micro Service와 소통하며 파이프라인을 진행한다.
Service 계층에서는 아래와 같이 외부 API에 HTTP Request를 날리는 Util Function을 활용하여 쉽게 Micro Service와 소통할 수 있다.
// util/http_request.go
func HttpGet(url string) map[string]interface{} {
// ...
}
func HttpPost(urlStr string, data map[string]interface{}) map[string]interface{} {
// ...
}
func HttpStream(urlStr string, data map[string]interface{}) <-chan []byte {
// ..
}빠르게 서버를 옮긴 뒤 부하 테스트를 같은 환경에서 진행했더니 테스트 성공 비율이 99.2% 까지 올랐다.
같은 테스트를 10번 더 진행했는데, 아래 99.2% 빼고는 전부 100%가 나왔다.

또한 Python 기준 멀티 빌드하면 15분까지 걸리곤 했는데, Go는 1분 이내로 전부 완료되었다.
용량도 10MB 이내로 매우 가벼워서 재배포도 부담없이 할 수 있었다.

3. 다시 Python으로 – Websocket 없애기
Go로 만든 채팅 웹소켓 서버는 큰 버그 없이 몇 주 정도 잘 버텨주었고, 그 뒤로는 채팅이 동작 안 된다는 버그 리포트를 받은 적이 없다.
기술적으로 보면 서버는 충분히 안정적이었으나, “웹소켓이 정말 AI 채팅 서비스에 적합한가?” 라는 고민을 계속 하게 되었다.
- 일반 채팅은 웹소켓으로 짜야 하는 것이 자명하지만, 우린 단방향 소통이었기 때문에 고민을 하게 되었다.
처음 WebSocket을 선택한 이유는 채팅방 대기 큐와 채팅 상태를 하나의 연결 상태로 관리해야 했기 때문이다.
그래서 연결이 유지되는 동안 대기 상태 → 채팅 상태로 자연스럽게 전환되는 Stateful한 서버를 만들었다.
하지만 나는 처음부터 WebSocket을 적용하는 거에 대해 회의감을 가지고 있었다.
그 이유는 채팅을 나누는 시간보다 채팅을 읽거나 치는 시간이 훨씬 더 길었고, 데이터를 주고받지 않는 시간에도 연결을 계속 유지하고 있어야 하기 때문이다.
Mixpanel로 분석한 평균 유저 세션 시간이 27분 정도 되고, Heavy User 같은 경우에는 몇 시간 동안 채팅을 하기도 한다. (실제로 6-7시간 동안 계속 쓴 유저도 있다.)
긴 세션 시간 동안 실제로 데이터를 주고받는 시간은 전체의 8% 정도 밖에 되지 않았고, 92%의 시간은 아무 데이터가 오가지 않는데도 연결을 계속 유지하고 있어야 한다.
그래서 나는 주고받는 데이터에 비해 State가 지나치게 강한 서버라는 생각을 하게 되었다.
게다가 네트워크 문제가 생기거나 서버가 재배포되었을 때 연결이 끊기게 되면 그 사용자는 그 채팅방에서 채팅을 더 이상 하지 못 하게 된다.
재연결 요청을 해도 같은 세션임을 인식하지 못하기 때문에 다시 큐에 들어가버린다.
이 구조를 근본적으로 바꾼 계기는 LLM Provider를 변경하면서였다.
우리는 Featherless에서 OpenRouter로 LLM Provider를 바꿨다.
우선 다양한 모델을 유연하게 제공할 수 있게 되었고, Pay-as-you-go Pricing으로 대기 없이 항상 빠른 응답이 가능해졌다.
대기를 할 필요가 없어졌기 때문에 Queue를 둘 이유가 사라졌다.
더 이상 대기 현황을 실시간으로 보여줄 필요가 없고 채팅방과 대기 큐의 강한 종속성도 아예 사라졌다.
이제 클라이언트가 서버와 연결을 유지해야 하는 순간은 메시지를 보낼 때 밖에 없고, 이때는 지속적인 양방향 통신이 아니라 단방향으로 요청을 보내고 응답만 스트리밍으로 받으면 된다.
이건 굳이 웹소켓 없이도 HTTP 기반의 Streaming Response로 충분히 구현할 수 있다.
그래서 나는 과감하게 메인 WebSocket 서버를 제거하고 FastAPI(Python)로 작성된 Inference Server의 API를 클라이언트에서 직접 호출하도록 구조를 바꾸었다.
이 결정은 한 이유는
- 웹소켓이 필요가 없어졌고, 상태 유지를 위해 복잡하게 얹었던 인증 로직도 제거 가능
- 기존에 작성되어있는 Inference API는 JWT 기반 인증을 하기 때문에 별도의 인증 레이어를 다시 만들 필요가 없음
- 이미 Streaming Response로 작성되어 있는 API를 두고 Wrapper를 새로 만들거나 구조를 바꿀 필요 없이 API를 외부로 노출만 해주면 됨
그래서 EKS에 Inference API를 외부로 노출하는 Load Balancer를 배포하고, 프론트에서는 WebSocket 관련 코드를 전부 제거하고 메시지 전송 시 Streaming Response로 응답을 수신하도록 바꿨다.
이렇게 하면 사용자가 채팅방에 몇 시간을 머물러도 문제가 없다.
서버 재배포나 네트워크 이슈가 있어도 채팅방 전체가 깨지지 않고, 에러가 나더라도 해당 채팅 요청 하나만 실패하게 된다.
채팅방 자체가 상태를 가지지 않게 되면서, 이제는 정적인 페이지와 크게 다를 바 없는 구조가 되었다.
마무리
더 좋은 구조를 위해 힘들게 도입했던 웹소켓을 과감히 덜어냈다.
그리고 계속 좋은 구조를 만들기 위해 웹소켓이 필요했던 이유를 다시 생각하고, 대기큐가 정말 필수였는지를 계속 생각하여 결국 문제 정의 자체가 바뀌었음을 받아들였다.
돌아가보면 모든 선택에는 이유가 있었다.
- 초기 Python 웹소켓 서버는 개발 공수를 고려한 선택이었다.
- 이후 Go로 옮긴 것은 로드 테스트 이후 Event Loop 한계를 체감하고, 소켓 처리에 더 효율적이고 나와 잘 맞는 언어/프레임워크를 찾는 과정에서 나온 선택이었다.
- 다시 Python으로 돌아온 것은 서비스 상황이 변해 웹소켓 자체가 필요 없어졌고, 그 상태에서 개발과 운영 공수를 최소화하기 위한 선택이었다.
어떤 선택도 이 기술이 좋거나 대세라서 이루어지지 않았다.
- 사실 시간이 너무 없었어서 대세라는 이유로 고르면 러닝 커브 때문에 더 힘들었을 거 같다.
프레임워크나 언어는 나에게는 목표가 아니라 도구다. 도구는 상황에 따라 바뀌어야 하고, 문제가 달라지면 선택 역시 달라져야 한다.
정해진 틀 안에서 문제를 잘 해결하는 능력도 중요하지만, 때로는 그 틀 자체를 의심하고 문제 위에서 더 큰 구조를 바라보는 시선이 더 중요할 때도 있다.
당연하다고 생각했던 구조가 사실은 서비스 전체를 더 단순하고 안정적으로 만들 기회를 가리고 있었을 수도 있기 때문이다.
결국 개발자에게 필요한 근본적인 역량은 특정 언어나 프레임워크에 대한 숙련도가 전부가 아니다.
언어와 프레임워크는 배우면 된다.
대신 문제의 본질을 다시 정의할 수 있는 힘, 현재 상황에서 감당 가능한 Trade-off를 판단하는 능력, 사용할 수 있는 도구들 사이에서 유연하게 선택하는 사고력이 본질적으로 더 중요하다고 생각한다.
- 특히 나는 시간과 돈이 극한으로 한정되어 있는 환경에서 경험을 하고 있기 떄문에 더 빨리 배우지 않았나 싶다.
무엇보다 좋은 것은 새벽에 일어날 일이 크게 줄었다. (^_^)



댓글 남기기