FastAPI를 도입하면서 : 코드 최적화부터 테스트까지

FastAPI를 도입하면서 : 코드 최적화부터 테스트까지

Tech

들어가며

안녕하세요, 숨고 Backend Engineer Flynn입니다.

숨고는 고객과 고수분들이 자유롭게 의견을 나눌 수 있는 숨고 커뮤니티를 약 2년 가까이 운영하고 있습니다. 이 기능은 숨고 서비스에서 처음으로 FastAPI를 도입하여 개발된 프로젝트로서, 현재까지 지속적으로 유지보수와 개선 작업을 이어오고 있습니다. 이번 글에서는 FastAPI를 도입한 이유와 일련의 과정들, 그리고 운영 중 겪었던 도전과 배운 점들에 대해 공유하고자 합니다.

FastAPI의 도입 배경과 진행 과정

FastAPI 도입 배경

FastAPI를 도입하게 된 가장 큰 이유는 서비스 성능과 개발 효율성을 극대화하기 위함이었습니다. 조직에서 새로운 도메인의 요구사항을 빠르게 충족하고 보다 유연한 구조를 설계하는 니즈가 있었고, 이런 맥락에서 평소 관심이 있던 FastAPI를 본격적으로 검토하여 도입하게 되었습니다.

FastAPI는 다음과 같은 강력한 장점이 있습니다

  • 의존성 주입을 통한 코드 재활용성 향상
  • Pydantic 기반 데이터 검증 및 타입 힌트를 활용한 IDE 친화적인 개발 환경
  • 라우터 작성만으로 자동 생성되는 SwaggerRedoc 문서
  • 뛰어난 비동기 처리 성능

이러한 장점들은 개발 전 테스트에서도 확인되었고, 이를 바탕으로 FastAPI를 커뮤니티 프로젝트에 도입하게 되었습니다.

FastAPI와 비동기로 개발하기

FastAPI는 기본적으로 Flask와 비슷한 방식으로 개발할 수 있지만, 타입 힌트를 추가하면 자동으로 API 문서가 생성되는 점이 매우 편리했습니다. 특히 복잡한 요청 처리 시에 Pydantic 모델을 사용해 유효성 검사를 자동화하고, 내부 로직에서도 객체를 통해 데이터를 간편히 다룰 수 있었습니다. 또한 공식 문서에 제시된 가이드라인 덕분에 테스트 코드 작성도 빠르게 진행할 수 있었습니다.

하지만 사내에서 처음 비동기 프레임워크를 사용하다 보니 몇 가지 문제점도 발생했습니다:

1.기존 코드베이스와의 호환성 문제

예를 들어, sqlalchemy는 1.4 버전부터 비동기 처리를 지원했지만, 기존 방식과는 다른 방식으로 쿼리를 작성해야 했습니다.

  • 기존 동기 코드 :
session.query(User).filter(User.id == user_id).first()
  • 비동기 코드 :
stmt = select(User).where(User.id == user_id) result = await session.execute(stmt) user = result.scalars().first()

2.MongoDB 비동기 처리

  • 기존에 Chapter에서 사용하던 pymongo 대신 비동기 라이브러리인 motor로 전환해야 했습니다
  • Pydantic 모델과의 호환성을 위해 자체 ODM(Object Document Mapping)을 구현해 사용했습니다 이러한 초기 시행착오에도 불구하고 커뮤니티 프로젝트는 성공적으로 운영 환경에 배포되었으며, 이후에도 안정적으로 서비스를 제공할 수 있었습니다. 이후에는 비동기 MongoDB ODM 라이브러리인 beanie를 도입했습니다. 기존에는 ODM을 직접 구현해서 사용했기 때문에 필요한 기능이 있을 때마다 만들어서 사용해야 했지만, 잘 만들어진 라이브러리를 사용해 생산성을 더욱 높일 수 있었습니다.

FastAPI 0.100 버전 업데이트

커뮤니티 프로젝트는 처음 FastAPI 0.73 버전으로 시작해 지속적으로 패키지를 업데이트하며 관리 해왔습니다. 그러던 중 FastAPI가 0.100 버전에서 내부적으로 큰 변화를 맞이했는데요. 내부에서 사용하는 Pydantic이 V2로 업그레이드되었습니다.

Pydantic V2는 Rust 기반으로 재작성되어 성능과 기능 면에서 많은 개선이 이루어졌지만, 기존 V1 기반 코드와 완벽하게 호환되지 않아 대규모 마이그레이션 작업이 필요했습니다. 이를 위해 Pydantic 팀에서 제공한 bump-pydantic 패키지를 활용해 기본적인 마이그레이션을 진행했습니다. 하지만 프로젝트 전반에 걸쳐 Pydantic을 복잡하게 사용하고 있었기에 스크립트 작업만으로는 한계가 있었고, 약 3주간의 긴 작업 끝에 마이그레이션을 완료하여, 높은 테스트 커버리지에 기반한 안정성을 확보할 수 있었습니다.

대체 이미지
FASTAPI 0.100 업데이트 당시 PR 크기

테스트 코드 개선

테스트 코드는 커뮤니티 프로젝트 초기부터 신경 썼던 지점이었지만, 테스트 실행 시간이 점점 길어지면서 불편함이 생겼습니다. 어느 순간 테스트 실행 시간이 약 10분에 달했고, 이는 개발 속도를 저하하는 주요 원인이 되었습니다.

대체 이미지
프로젝트 초기 평균적인 테스트코드 실행 속도

이를 해결하기 위해 다음과 같은 개선 작업을 진행했습니다 :

1.병렬 실행

  • pytest-xdist 패키지를 활용해 테스트를 병렬로 실행하며 실행 시간을 절반 정도 줄일 수 있었습니다
  • 하지만 목 데이터 스코프 설정 문제로 인해 간헐적 테스트 실패가 발생했습니다

2.목 데이터 최적화

  • 목 데이터 생성 과정에서 불필요하게 자원이 소모되는 부분을 최적화했습니다
  • 병렬 실행 중 삭제되는 목 데이터를 보완하여 간헐적 실패 문제를 해결했습니다

결과적으로 전체 테스트 실행 시간을 약 10분 → 1분대로 단축할 수 있었습니다.

대체 이미지
pytest-xdist 적용 후 테스트코드 실행 속도
대체 이미지
목 데이터 최적화 후 테스트코드 실행 속도

FastAPI를 사용하며 느꼈던 점들

좋았던 점

  • Pydantic 기반 데이터 검증 : 유효성 검사를 자동화해 개발 실수를 크게 줄였고, 속성 규칙 추가도 용이했습니다
  • Background Tasks : 클라이언트 대기가 필요 없는 작업을 비동기로 처리할 수 있어 간단한 작업에서 유용했습니다
  • 잘 작성된 문서 : 공식 문서가 체계적이고 최신 상태로 유지되어 개발 과정에서 큰 도움이 되었습니다

아쉬웠던 점

  • SQLAlchemy와 Pydantic 간 호환성 : MongoDB의 경우 beanie 덕분에 Pydantic 모델 사용이 편리했지만, RDB에서는 SQLAlchemy와 Pydantic 간 변환 과정이 번거로웠습니다
  • 프로젝트 구조 설계 : 초기 설계 방식은 프로젝트 확장성이 부족했고, 이후 여러 차례 구조 변경 작업을 거쳐야 했습니다

배웠던 점

  • 적절한 기술 선택의 중요성 : 새로운 도메인 요구사항에 맞는 기술 스택을 선택하여 개발 속도와 유지보수성을 크게 향상시킬 수 있었습니다
  • 테스트 커버리지의 가치 : 대규모 업데이트나 마이그레이션 경험을 통해, 높은 테스트 커버리지가 대규모 변경에서 안정성을 크게 높여준다는 점을 실감했습니다
  • 지속적인 개선의 중요성 : 처음부터 완벽한 설계는 불가능하며, 지속적인 개선이 프로젝트 성장의 핵심이라는 점을 배웠습니다

마치며

숨고 커뮤니티 프로젝트에서 FastAPI를 도입하고 운영한 경험은 기술적 도전과 성장을 동시에 가져다준 일이었습니다. FastAPI는 뛰어난 성능뿐 아니라 개발자 친화적인 기능 덕분에 빠르게 요구사항을 충족시킬 수 있는 강력한 도구였습니다.

물론 위에서 언급했던 아쉬운 점들과 해결해야 할 과제들도 존재했습니다. 그러나 이런 어려움을 극복하며 더 나은 코드 품질과 효율적인 구조를 구축할 수 있었고, 더 나은 코드, 더 나은 구조를 작성할 수 있었습니다.

무엇보다 중요한 것은 ‘완벽한 시작’이닌 ‘지속적인 개선’이라는 점입니다. 처음부터 모든 것을 완벽히 준비할 순 없지만, 변화와 성장을 두려워하지 않고 꾸준히 개선해 나간다면 더 나은 결과를 만들어낼 수 있다는 믿음을 가지게 되었습니다.

앞으로도 저희는 끊임없이 발전하며 더 나은 서비스를 제공하기 위해 노력하겠습니다.

감사합니다.

  • #backend
  • #engineering
  • #fastapi
  • #soomgo
  • #tech
Flynn Park
Flynn ParkBackend Engineer

모두의 더 나은 삶을 위해
함께 변화를 만들어갈 동료를 기다립니다

채용중인 공고 보기