초기 스타트업이라면 더더욱 PostgreSQL을 선택하자

#postgresql, #mongodb

왜 MongoDB를 사용하는가?

유연한 스키마: 스키마 변경이 쉬워 요구사항이 자주 변하는 서비스에 유리하다.
수평적 확장: 샤딩(Sharding)을 통해 데이터 분산 및 확장이 용이하다.
개발 용이성: JS과 유사한 쿼리 문법과 JSON과 유사한 문서 구조 덕분에 빠른 개발이 가능하다.

그럼 왜 PostgreSQL을 선택하는가?

데이터 무결성: ACID 트랜잭션 및 스키마를 통한 높은 데이터 정합성이 보장된다.
복잡한 쿼리: SQL 지원 및 고급 쿼리 최적화가 가능하다.
다양한 기능: JSON 지원, 지리공간 데이터, 확장성 등 풍부한 기능이 있다.

초기 스타트업의 실수

  • 나는 현재 재직 중인 회사도 그렇고 이전에 다녔던 회사들도 그렇고 개발자 출신 대표가 운영하는 스타트업들을 경험했었다.
  • 그들의 기술적 역량과 지식 수준은 뒤로 하고, 하나같이 MongoDB를 선택했는데, 그 이유는 스타트업의 잦은 변화에 대응하기에는 MongoDB가 더 적합하다고 생각했기 때문이다.
  • 게다가 MongoDB라는 스택 자체가 보통 처음 웹 개발을 접할 때 한 번씩은 써보는 녀석이고 난이도 자체가 매우 쉬운 편이라 인건비가 낮은 초급 개발자들을 앉혀놓고 개발을 시키기에 적합했을 것이다.
  • 나는 이것을 지식의 저주라고 생각한다. 자신이 가지고 있는 기술적 인사이트를 접목하여 다른 스타트업과의 차별점을 만들고 싶었던 대표들은 하나같이 보편적이지 않은 접근법을 선택하곤 한다. (그리고 초기에 쉬운 길을 택한다.)
  • MongoDB는 초기 생산성에 많은 영향을 끼쳤을 것이라 생각되지만 중기 또는 장기적으로 보면 생산성의 저하를 불러오는 요소가 된다.
  • 기능이 추가되면 추가될수록 Join의 부담은 커지고 초급 개발자들이 MongoDB의 Aggregation을 접하기 시작하면 프로젝트는 마치 저주라도 받은 듯 느려지기 시작한다.
  • 결국 운영 레벨에서 속도 이슈가 주기적으로 보고되고 쿼리를 아무리 개선해도 한계가 없으며, Redis로 캐싱을 하자는 이야기까지 나오기 시작하면 "아 그냥 처음부터 SQL할걸" 하게 된다.

그럼 마이그레이션하지 그래요?

  • 현업에서 MongoDB를 PostgreSQL로 마이그레이션하는 일은 정말 큰 비용이 발생한다.
  • 지금이라도 SQL하자는 내 이야기를 무시하던 대표 또는 기술 책임자들은 하나같이 "기술 스택은 나중에 회사가 커지면 돈으로 해결할 수 있는 문제다." 라고들 말했다. 그래 뭐 인력을 갈아넣으면 안되는 일은 없다.
  • 하지만 현실은 SQL의 필요성을 느끼는 중기 운영 단계에서 이슈가 발생하고, 이때에는 기술적인 문제에 돈을 쓰기 애매한 경우가 많다. (신규 R&D가 더 중요한 사안이 되는 경우가 많음.)

인간은 같은 실수를 반복해요

  • 비슷한 이유로 나 또한 개인 프로젝트에서 MongoDB를 선택했었다.
  • SQL을 잘 몰랐으며, 당장에 알고 있는 지식으로 빠르게 구현하는 것이 중요했기 때문에 그렇게 했었다.
  • 중간 중간 SQL로 넘어갈 계획을 하기도 했지만 마찬가지로 새로운 기능과 안정성 강화 등 제품 자체에 집중해야 할 시기가 반복되면서 마이그레이션은 우선순위에서 밀리곤 했다.
  • 무엇보다 직감적으로 마이그레이션 작업이 쉬운 일이 아니라는 것을 알고 있었기에 엄두를 못낸 것도 있다.

그 힘든거 제가 해봤습니다

  • 요즘 개인 프로젝트의 데이터베이스를 MongoDB에서 PostgreSQL로 마이그레이션하는 작업을 진행하고 있다.
  • 이번 마이그레이션은 Claude Code와 함께 했다. 백엔드 코드 전체를 리팩토링해야 했기에 나는 설계와 전략에만 집중하고 코딩은 AI에게 맡겨 마이그레이션 작업을 최대한 빠르게 진행하고 있다.
  • MongoDB와 PostgreSQL은 NoSQL과 SQL의 차이만큼이나 구조적으로 크게 차이가 나기 때문에 마이그레이션 전략을 잘 세우는 것이 중요했다.
    • 예를 들어 MongoDB는 ObjectId라는 전용 타입으로 고유 ID를 부여하지만 PostgreSQL은 자동 증가하는 정수인 SERIAL 또는 UUID를 쓴다. 성능적으로 SERIAL이 유리하기에 게시물은 SERIAL을 쓰고 싶었지만 기존 MongoDB의 ObjectId와의 호환성을 위해 모든 데이터들의 Primary Key를 UUID로 맞출 수 밖에 없었다.
  • 최대한 기존 프론트엔드가 요구하는 DTO에 맞추려고 했으나 새롭게 개선된 구조도 있고, MongoDB와의 차이점이 큰 탓에 프론트엔드도 대대적인 수정이 필요했다.
  • 마이그레이션 툴의 개발도 이슈였는데 Node 기반(런타임은 Bun)으로 개발했더니 마이그레이션 속도가 너무 느렸다. 그리고 원본 서버(MongoDB)에 지속적으로 요청을 쏘는 바람에 CPU가 치솟는 문제도 발견됐다.
    • 이를 해결하기 위해 원본 서버에서 컬렉션 내 모든 도큐먼트를 첫 타임에 전부 가져온 다음 로컬 파일로 저장하여 사용하도록 했다.
    • 그럼에도 속도가 너무 느렸는데, 이를 해결하기 위해 PostgreSQL의 BulkInsert 기능을 사용하여 배치 단위별로 여러개를 한 번에 등록하도록 했다.
    • 아직 현재 진행형인 이슈인데 MongoDB와 PostgreSQL은 ID 체계가 다르다. MongoDB의 ObjectId를 PostgreSQL의 UUID로 변환하는 작업이 필요했고, 관계 형성을 위해 id-mapping이라는 테이블을 추가하여 관리했다.
    • 이렇게 하니까 데이터의 무결성은 지킬 수 있지만 속도가 너무 느렸다. 매번 MongoID에 대한 id-mapping이 존재하는지 확인해야 했기에...
    • 이것도 캐싱 전략으로 해결해볼 수 있을 것 같아서 시도해보고 있다.
  • 최종적으로는 Go 기반의 마이그레이션 툴을 다시 개발하고 있다. Go의 경우 현업에서 주기적으로 파일을 수집하여 업로드해주는 배치 서버를 개발할 때 사용했었는데 고루틴을 사용한 동시성 개발이 가능하다.

© 2026 CalvinSnax.