✏️ 갑자기 서버, 그것도 Python 서버를 개선하게 된 계기
저는 프론트엔드 개발자입니다.
제가 재직중인 회사는 SI/SM 스타트 기업으로 스타트업 & SI/SM 회사의 특징으로 필요하다면, 혹은 해야한다면 응당 누군가는 반드시 기술을 구현해서 서비스를 제공해야합니다.
고객사 측에서 유지보수/고도화를 진행하고 있는 🔗LMS 플랫폼에서 작성한 사업계획서를 시장에서 가상 검증이 가능하도록 LLM을 탑재한 페르소나형 챗봇 서비스를 요구했고 고객사와 회의 중 대표님께서 기술 스택으로 확장성을 고려하면 Python이 좋겠다고 이야기 했고 이내 신입 Python개발자를 채용해주셔서 개발이 순조롭게 진행되는 듯 하였습니다.
하지만 급하게 다른 프로젝트가 생겼는데 마침 요구 스택이 Python이었고 그렇게 production에 배포하기 전에 신입 python개발자는 사무실을 떠나 파견을 가버렸습니다...
당장 급한 상황에서 Python을 개발해본 사람은 아무도 없었고 대표님께서는 혹시 해보고 싶은 사람 있냐고 물어봤지만... 말이 쉽지 사실을 또 다른 언어를 한다는게 부담스러운게 사실이었습니다.
저는 오히려 지금이 Node.js 외 언어 서버를 직접 경험할 수 있는 기회다 싶었고 어차피 퍼블리싱만 되어있던 챗봇 화면까지 같이 통합해서 작업하면 개발효율도 좋으니 챗봇 서비스를 통합 구현하겠다고 전달 드려 승인받았습니다.
다른 서버 포지션도 아니고 프론트엔드 개발자이지만 제 🔗취준생 시절 포트폴리오가 풀스택이었고, 🔗Next.js Serverless Architecture를 활용한 풀스택 개발 경험을 믿고 맡기신 것 같았습니다.
🔗그렇게 AI 시장 검증 챗봇 서비스를 통합 개발하게 되었습니다.
📘 기초는 이해하자
Python을 처음 접하는 만큼 서버가 돌아가고 있으니 기존에 개발되어있는 코드에서 추가 기능와 요구사항을 적용하며 배울 수 있겠다 싶었습니다. 그렇게 프로젝트 소스를 받아 열어보았는데 일단 기본적으로 언어 자체가 이해하기 어려운 구조는 아니었습니다.
다만, 개발되어있던 프로젝트의 아키텍처가... 없었습니다.
단일 폴더에 내부에는 app.py와 .env만 존재하는 형태로 상당히 당황스러웠던 기억이 있습니다. 아마도 실무에서 프로젝트 처음 진행해보고 같은 언어를 사용하는 선임이 없어 방향성을 잡지 못한 상태였고, 초기 개발단계라서 그랬던 것 같았습니다.
분명 Python에도 디자인패턴과 지향하는 아키텍처가 있을 것이고 Python이라는 언어에 대해서 어느정도 이해를 하고 있어야 분명 개발할 때 해메지 않을 것일거라 생각하고 하루 날을 잡고 구현하며 학습하였고 다양하고 방대한 학습 자료들이 있었기 때문에 실무에 바로 도입하기 위한 전체적인 프로세스와 필요한 기능을 구현하기 위한 지식을 학습하기 위해 선택하여 집중하기로 하여 아래와 같이 집중 학습 내용을 정리했습니다.
1. 프론트엔드 개발자로서 JS가 익숙하다. "기본 작동방식에 차이점이 있는가?"
2. 패키지관리 파일이나 폴더가 보이지 않는다? "보편적으로는 라이브러리 패키지 관리를 어떻게 하는가?"
3. 자바에는 유명한 MVC패턴으로 관심사 분리를 한다. "보편적인 디자인 패턴이 있는가?"
4. 개발기간이 넉넉한 것이 아니기 때문에 개발 속도도 중요하다. "나에게 익숙한 Nest.js와 유사한 패턴이 있는가?"
우선 기본적으로 싱글스레드 기반 블로킹방식으로 작동한다는 사실을 알게 되었고, __init__.py 파일을 통해 내보내는 방식 또한 알게 되었습니다. 그리고 pip라는 패키지 매니저가 있으며, venv를 활용하여 node_modules처럼 프로젝트 단위에 패키지를 적용할 수 있다는 것을 알게 되었으며 " pip freeze > requirements.txt" 명령어를 통해 package.json처럼 프로젝트 패키지 스팩을 만들고 해당 스팩을 통해 명시 패키지만 다운로드할 수 있다는 사실을 알게되었습니다.
이렇게 하나씩 공부하며 챗봇 서버를 리팩터링을 우선으로 진행하였습니다.
📚 이가 없다면 잇몸으로, 커스텀 디자인패턴 도입
일단 개발기간을 산정했을 때 당장 MVP패턴과 유사하게 구성하는 것은 문제가 있었습니다.
때문에 4번째로 고민했던 부분인 나에게 익숙한 패턴이 있는가에 대해서 분석했을 때 디자인 패턴을 Nest.js와 유사하게 가져가기로 했습니다.
Controller, Service, Repository, Module로 관심사를 분리하려 했으나... 초기 러닝커브와 짧은 데드라인을 고려하여 Controller, Service만 관심사를 분리하기로 했습니다. 데이터 형을 지정하기 위한 class를 별도로 구성하지 않았던 상태여서 추가적으로 도입할 경우 꽤나 작업이 커지기 때문에 Model을 생략하였고(기존 코드 그대로 타입 직접 명시) Module의 경우 마찬가지로 Python에 대한 깊은 이해도 없이 접근하기에 무리가 있어 보여 생략하였습니다.
추가적으로 확장 가능성에 대한 부분도 염두에 두어 조금 더 세분화하고 관리에 용이하도록 src폴더로 감싸고 config, main, etc 등으로 커다란 카테고리를 구분했습니다.

임시 디자인 패턴이라 아쉽기도 하고 추 후에 Python 개발자 채용 계획이 있다고 하셨으니 그때 한번 제대로 배워보고 싶다는 마음가짐도 생겼습니다.
❓ LLM 챗봇인데 취소기능이 없다
현재 종사하고 있는 회사에는 기획자와 PM이 없습니다.
때문에 각자 주포지션은 있지만 기획은 매번 다 함께 할 수 밖에 없는 환경이다보니 놓치는 기획이나 당연히 있어야할게 없어서 뒤늦게 추가되는 경우도 종종 있었습니다.
이번이 그런 상황이었습니다. 디자인까지 모두 완성되었지만 취소 버튼이 디자인 되어있지 않아 개발을 하고 나서 QA를 하는데 "어라? 생각해보니 GPT에는 취소 기능이 있는데?" 라는 생각을 하게 되었습니다.
본질적으로 "왜 필요한가?"를 먼저 생각해 보았습니다.
스스로 떠올린 첫 번째 케이스는 바로,
당시 LLM API 특성상 응답이 꽤나 느렸고 만약 응답을 기다리다 응답을 받지 못한 상태에서 브라우저를 급하게 끄거나 다른 화면으로 이동하는 케이스를 생각해보았습니다.
그렇게 시간이 흘러 어느날 다시 챗봇에게 질문하기 위해 사이트에 접속해서 보았는데 "어라? 나는 이런 질문을 한 적이 없는데 왜 이런 질문과 결과가 있지?" 라는 불안감이 들기 시작할 수 있을 것 같습니다.
당시 상황이 급박하여 기억에서 희미해질 수 있기 때문에 오히려 요청 취소기능은 반드시 있어야겠다는 생각이 들었습니다.
두 번째 케이스로는 단순히 사용자가 잘못보내서 취소하고 싶은 경우입니다.
너무 당연한 이유이기 때문에 부연설명은 하지않겠습니다...
네, 이렇게 고민해보니 UX를 생각했을 때 당연하게 있어야 할 기능으로 보입니다.
때문에 이에 대한 문제를 공론화하고 개발을 진행하기로 했습니다.
❓ 요청 취소가 안된다
이유는 아주 단순했습니다. Python은 싱글스레드 동기 작동방식으로 Blocking 처리 방식입니다.즉, get 요청이 들어왔다고 가정하면 해당 요청이 끝날 때까지 다음 요청은 Blocking되어 실행되지 않고 기다렸다가 작업이 끝나야 다음 작업을 실행하게 됩니다.
그렇다면 취소를 하기 위해서는 작업중인 매서드에 간섭 가능하도록 제어권을 가져야 하기 때문에 이벤트 루프의 존재가 필수 불가결입니다.
기존에 사용하고 있는 라이브러리를 확인해보니 pysql, requests를 활용하여 모두 동기식으로만 처리하고 있어 요청 취소가 불가능한 구조였기 때문에 프론트엔드에서 제 아무리 AbortController를 통해 통신을 취소하려 해도 불가능했던 것이였습니다...
🛠️ Blocking ➔ Non-Blocking 전환
통신 관련 라이브러리를 [ pysql, requests ➔ aiomysql, aiohttp ] 로 전환하여 비동기 처리가 가능한 awaitable 구조로 전환했습니다. 코드 전체를 확인하며 컨버팅을 해야 해서 꽤나 대규모 작업이었지만 할 수 밖에 없는 상황이니 만큼 컨버팅을 진행했습니다.
그렇게 http요청과 I/O 작업 시 await을 통해 작업 제어권을 가질 수 있는 구조가 되어 챗봇 요청 취소 기능을 구현했습니다.
추가적으로 non-blocking의 특징으로 동시성 처리 효율이 확보되어 많은 사용자가 동시간대에 요청을 했을 때 이전 구조보다 명확하게 응답대기 시간이 줄어들었습니다.
스스로 생각해봤습니다. 그렇다면 얼마나 차이가 날까?
그래서 K6를 활용하여 200건 동시 요청 통해 확인해 보았습니다.

기존의 Blocking 방식은 200건 동시 요청 시 모두 응답받기까지 12.5초정도의 시간이 소요됩니다.

Non-Blocking 구조에서는 총 응답 시간이 7.4초로 감소하여 약 1.7배의 성능 개선을 보여주는 것을 확인할 수 있었습니다.
❗ 로그인 상태를 검증하지 않는다
기존 구성되어있던 서버에서 회원 전용 기능임에도 요청 header에 담긴 토큰을 검증하는 로직이 없어 비로그인자도 요청하고 응답을 받을 수 있는 문제를 발견했습니다.
이는 매우 치명적인 문제로 즉시 도입하기 위해 기존 main API 서버인 Java 서버에 존재하는 토큰 검증 endpoint로 요청하여 공용으로 활용 가능하도록 auth_guard라는 데코레이터로 구현하고 재사용하여 문제를 해결했습니다.
🚩결과 정리
다음과 같은 문제와 해결책을 강구하여 성과를 얻었습니다.
- 디자인패턴이 없이 app.py에 모든 코드가 작성되어 유지보수 어려운 형태
➔ 짧은 데드라인을 고려하여 가장 익숙한 Nest.js 디자인 패턴을 모방하여 커스텀 디자인 패턴 적용 - 챗봇 요청 취소 기능이 없음
➔ 문제를 공론화 하여 기획 추가 및 기능 구현 - Python 서버가 동기적으로만 작동하여 취소를 할 수 없는 문제
➔ [ requests ➔ aiohttp ], [ pysql ➔ aiomysql ] 비동기 처리가 가능한 라이브러리로 마이그레이션
( 200건 기준 동시 요청 처리 효율 1.7배 향상 ) - 로그인 상태를 검증하지 않는 문제
➔ 짧은 데드라인을 고려하여 main API Java서버에 존재하는 토큰 검증 endpoint로 요청하여 검증하는 auth_guard 데코레이터를 구현하여 재사용
'Dev Logs' 카테고리의 다른 글
| [ React + Vite & NginX: Gzip Compression ] 번들 사이즈 최적화로 UX향상 시키기 (2) | 2025.11.22 |
|---|---|
| [ React: PDF Generator ] 맞춤형 PDF 렌더링 성능 최적화 (2) | 2025.11.20 |
| [ Three.js Camera handling ] Map화면 이탈 현상 막기 (0) | 2025.11.14 |
| [ openapi-generator-cli ] 사내 API 생성 자동화 도입기 (3) | 2025.11.13 |
| [ Error Fix ] Unable to find git in your PATH. (2) | 2025.01.01 |