본문 바로가기

카테고리 없음

[AI 번역] 마이크로서비스 없이 모놀리식 장고 프로젝트를 확장하는 방법

원본 : https://medium.com/@finndersen/how-to-scale-a-monolithic-django-project-6a8394c23fe8

 

유지 관리가 쉽고 높은 부하를 처리할 수 있도록 성장하는 Django 프로젝트를 설계하는 방법을 살펴봅니다.

 

 

The Great Dilemma  Source

 

수년에 걸쳐 복잡성이 커진 데이터 플랫폼의 기술 책임자로서 저는 소프트웨어 애플리케이션을 가장 잘 구조화하여 작업하기 쉽고 성능이 우수하도록 하는 방법에 대해 끊임없이 고민하고 있습니다. 이 플랫폼은 프론트엔드 웹 애플리케이션과 백엔드 데이터 처리 작업으로 구성되어 있으며, 둘 다 최종 사용자에게 다양한(대부분 독립적인) 서비스를 제공할 수 있도록 지원합니다. 이러한 모든 구성 요소를 동일한 코드 저장소 내에서 서로 연결하고 구현하는 것이 번거롭게 느껴지기 시작했기 때문에 애플리케이션 디자인이 발전함에 따라 이를 가장 잘 관리할 수 있는 방법에 대해 고민해야 했습니다. 다양한 컴포넌트를 독립된 패키지로 분리하는 것은 까다롭고 많은 작업이 필요해 보였는데, 그만한 가치가 있을까요? 이 글에서는 제 여정에서 얻은 몇 가지 인사이트와 교훈을 공유하고자 합니다.

 

모노 또는 마이크로로 전환

모놀리식 소프트웨어 아키텍처에서는 애플리케이션의 모든 서비스와 구성 요소가 하나의 거대한 코드베이스로 묶여 있으며, 서로 긴밀하게 결합되어 있는 경우가 많습니다(서로 크게 의존하는 경우가 많음). 이러한 설계는 소프트웨어 시스템의 개발 또는 성장 초기 단계에서 종종 발생하며 일반적으로 나쁜 것으로 간주됩니다:

 

  • 애플리케이션의 일부를 변경하려면 전체 코드베이스를 다시 빌드, 테스트 및 배포해야 하므로 배포 일정이 짧아질 수 있습니다.
  • 모든 모듈/서비스가 동일한 언어를 사용해야 합니다.
  • 민첩성 저하 - 새로운 모듈/서비스에 새로운 언어/기술/프레임워크를 채택하기 어려움
  • 더 큰 결함 확산 반경 - 하나의 문제가 전체 시스템에 영향을 미칠 가능성이 더 큼
  • 많은 개발자가 하나의 대규모 코드베이스/리포지토리에서 협업하는 것이 어려워질 수 있음
  • 서로 다른 모듈/서비스가 결합되어 있기 때문에 독립적으로 확장하기 어려울 수 있습니다.

 

모놀리스주의의 병폐에 대한 보편적인 해독제는 위에 나열된 단점을 해결하는 것을 목표로 하는 잘 알려진 마이크로 서비스 아키텍처입니다. 여기에는 비즈니스 도메인, 기능 또는 리소스 소유권에 따라 애플리케이션을 여러 개의 작은 독립 서비스로 분리하는 것이 포함됩니다. 이러한 서비스는 느슨하게 결합되어 있으므로 직접 코드를 가져오거나 함수를 호출하는 대신 표준화된 언어에 구애받지 않는 외부 인터페이스(예: REST API)를 통해 상호 작용합니다. 마이크로서비스 아키텍처 패턴에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

모놀리스를 마이크로서비스로 분할하기 - 이렇게 쉬울 수만 있다면 (소스)

 

마이크로서비스 아키텍처로 마이그레이션하면 많은 이점을 얻을 수 있지만, 종종 어렵고 복잡한 프로세스로 인해 가치보다 더 많은 문제가 발생할 수 있습니다. 장고 애플리케이션의 경우, 각 개별 서비스가 별도의 장고 프로젝트가 되어야 하며, 그 결과 추가적인 중복 및 관리 오버헤드가 발생하게 됩니다:

 

  • 일반적인 공유 코드(라이브러리 종속성, 기본 클래스(모델, 뷰, 양식 등), 기본 템플릿, 유틸리티 또는 헬퍼 함수 등)
  • CSS 및 기타 정적 파일
  • 테스트 및 배포 인프라
  • 로깅 및 알람
  • 구성 관리
  • 버전 제어 및 변경 관리
  • 데이터베이스 인프라 및 관리(마이그레이션, 백업, 고가용성 등...)
  • 인증 및 보안
  • 다른 서비스의 데이터 또는 기능에 액세스하기 위한 API

즉, 기본적으로 각 서비스를 개발하고 관리할 전담 팀이 필요합니다. 또한 여러 개의 분리된 서비스를 실행하기 위해 추가 서버 인프라를 도입하거나 이를 관리하기 위해 Kubernetes와 같은 컨테이너 오케스트레이션 플랫폼을 도입해야 합니다. 또한 서로 다른 모듈의 모델 간에 외래 키 관계를 가질 수 없으므로 데이터베이스 수준의 제약 조건 무결성 및 조인 쿼리 기능이 손실됩니다.

마이크로서비스 아키텍처로 마이그레이션하고 관리하는 일은 부담스러울 수 있습니다. (Generated by  DALL-E)

 

따라서 마이크로서비스 마이그레이션을 시작하기 전에 올바른 이유로 마이그레이션을 진행하고 있는지, 그리고 이를 수행할 수 있을 만큼 유능하고 규모가 큰 개발팀이 있는지 확인해야 합니다. 마이크로서비스가 유행이라고 들었고 경영진 동료들에게 마이크로서비스를 과시하고 싶어 하는 경영진의 압력 등 마이크로서비스 도입에 반대하는 패턴에 굴복하지 않도록 주의해야 합니다.

모놀리스 수용

이 블로그 게시물에서는 마이크로서비스의 이점에 대한 몇 가지 잘못된 인식과 모놀리식 설계로 얼마나 많은 이점을 얻을 수 있는지에 대해 살펴봅니다. 모놀리식 접근 방식에는 실제로 많은 이점이 있습니다. Google조차도 모든 제품에 대규모 모노 리포지토리를 사용하고 있으며(그 강력한 이유에 대해서는 백서를 읽어보세요), Instagram은 Django 모놀리스로 뒷받침되고 있습니다. 이점은 다음과 같습니다:

 

  • 복잡성 감소(독립적인 구성 요소와 움직이는 부품이 적음)
  • 서로 다른 서비스/모듈 간의 상호 작용/인증에 대한 오버헤드 감소
  • 서비스/모듈 간 공통 코드 공유 및 재사용
  • 전체 프로젝트에 걸쳐 통합된 버전 관리 및 조정을 통한 단일 소스 관리
  • 모듈 간 리포지토리 경계가 없는 코드 가시성 및 명확한 프로젝트 구조
  • 배포, 관리 및 유지 관리가 더 쉬워짐
  • 더 쉽고 빠른 개발
  • 더 쉬운 엔드투엔드 테스트

플러스 사이즈 프로젝트가 그렇게 나쁘지 않을 수도 있습니다 (출처)

마이크로서비스 소프트웨어 아키텍처로 마이그레이션하는 것이 반드시 직면할 수 있는 확장성 문제에 대한 손쉬운 해결책은 아닙니다. 다른 사람들이 모두 그렇게 하고 있는 것처럼 보인다고 해서 그것이 특정 애플리케이션에 가장 적합한 접근 방식이라는 의미는 아닙니다. 닐 포드는 그의 책과 이 프레젠테이션(꼭 확인해 보시길 권합니다)에서 소프트웨어 아키텍처는 각 사례에 따른 장단점을 고려해야 하기 때문에 일반적인 조언은 사실상 없다고 말합니다.

 

"소프트웨어 아키텍처에 대한 일반적인 조언은 없습니다. 일반적인 소프트웨어 아키텍처가 없기 때문입니다." - Neal Ford

 

전체 마이크로서비스 아키텍처로 마이그레이션하는 것은 많은 경우에 벅찬 작업이며 아마도 과도한 작업일 수 있으므로, 대신 모놀리식 Django 애플리케이션을 조각으로 나누지 않고 확장성 문제를 해결하는 몇 가지 방법을 살펴봅시다. 결국, Google의 모노 레포에 대한 논문의 요점을 기억할 가치가 있습니다:

 

"모놀리식 코드베이스는 결코 모놀리식 소프트웨어 설계를 의미하지 않습니다."

개발 문제를 해결하는 방법

다음은 코드베이스와 개발 팀의 규모가 커짐에 따라 발생할 수 있는 모놀리식 소프트웨어 애플리케이션 개발 및 유지 관리 문제를 방지하는 몇 가지 방법입니다.

모듈식 프로젝트 구조 사용

애플리케이션의 여러 모듈을 완전히 독립적인 서비스와 리포지토리로 분리하지 않고도 좋은 소프트웨어 설계 관행을 따르고 느슨하게 결합된 모듈식 구조를 가질 수 있습니다. 먼저 비즈니스 도메인, 기능 및 리소스를 기반으로 이러한 모듈의 경계가 어디에 있는지 파악해야 합니다. 특정 도메인이나 서비스에 대한 모든 비즈니스 로직은 동일한 모듈에 있어야 합니다.

모놀리식 애플리케이션의 구성 요소를 식별하고 모듈식 구조로 재배치하기(Neal Ford)

 

이 글에서는 리포지토리 내에서 코드 결합을 감지하는 데 사용할 수 있는 몇 가지 코드 포렌식 도구를 살펴보고, 소프트웨어 애플리케이션을 분할하거나 구성하는 방법을 결정할 때 팀 구조를 고려하는 것의 중요성에 대해 설명합니다. django-extensions 프로젝트에는 프로젝트의 모델과 모델이 어떻게 연결되어 있는지에 대한 다이어그램을 생성하는 graph_models 관리 명령이 포함되어 있어 결합 및 적절한 모듈 경계를 시각화하는 데 유용합니다.

 

django-extension의 graph_models 명령으로 생성된 모델 그래프 다이어그램의 예 (출처)

 

모듈은 독립된 Django 앱으로 배열되어야 하며(각각 INSTALLED_APPS 목록에 항목이 있음), 모듈 간에 종속성이나 결합이 없거나 최소화되어야 합니다. 다른 상위 도메인 중심 모듈에 종속되어 있고 다른 상위 도메인 중심 모듈에서 사용하는 공유 구성 요소 및 모델(예: 사용자 지정 사용자 모델)을 포함하는 하나 또는 몇 개의 핵심 모듈이 필요할 수 있습니다. 이 프로젝트 구조 예시를 참조하면 좋은 템플릿이 될 수 있습니다.

모듈은 비즈니스 도메인, 제품 또는 프로젝트와 연관되어야 해당 작업에 배정된 개발자가 코드베이스의 해당 섹션에 대한 소유권을 가질 수 있으며 애플리케이션 내의 다른 모듈에 대한 깊은 이해가 필요하지 않습니다. 이는 또한 서로 다른 모듈에서 작업하는 개발자가 코드 또는 Django 데이터베이스 마이그레이션 파일 충돌 없이 동일한 리포지토리에서 협업할 수 있음을 의미합니다. 특정 모듈이 변경되면 해당 테스트 케이스를 독립적으로 실행하여 효율성을 높일 수 있습니다:

python manage.py test module_name​

 

서로 상호 작용해야 하는 모듈 간의 분리를 달성하는 방법에는 원하는 정도에 따라 다양한 방법이 있습니다. 이 문서에서는 모듈 간에 직접적인 모델 액세스 및 데이터베이스 관계를 허용하지 않고(모듈 데이터베이스는 비공개), 대신 각 모듈이 기능이나 데이터에 액세스 할 수 있는 공용 인터페이스를 제공하는 엄격한 접근 방식을 설명합니다(마이크로서비스가 공용 API를 제공하는 것과 같은 방식). 개인적으로 저는 모듈 간 모델에 직접 액세스 하거나 관계를 맺을 수 없는 불편함과 공용 인터페이스의 오버헤드 때문에 이 정도의 디커플링은 플랫폼에 다소 과도한 수준이라고 생각합니다. 다시 말하지만, 이러한 종류의 설계 결정은 모두 장단점을 고려한 것이며 가장 적절한 솔루션은 각각의 경우에 따라 달라집니다.

일반적으로 모듈은 비즈니스 로직과 기능을 캡슐화하는 것을 목표로 해야 하며, 모듈 간의 모델 및 기타 코드에 대한 직접 액세스는 최소화해야 합니다. 모든 하드 종속성은 단방향이어야 하며, 모듈B가 모듈A에 종속된 경우 모듈A는 모듈B의 존재를 인식할 필요가 없어야 합니다.

프론트엔드와 백엔드 분리

Django 프로젝트에서 널리 사용되는 디자인 선택은 백엔드는 API 엔드포인트로만 기능하고(Django Rest Framework의 도움으로), 프론트엔드는 Angular, React 또는 Vue.js와 같은 JavaScript 프레임워크를 사용하여 완전히 분리된 단일 페이지 애플리케이션(SPA)으로 만드는 것입니다.

Django 백엔드와 별도의 Angular 프론트엔드 애플리케이션이 있는 아키텍처(출처)

 

여기에는 몇 가지 이점이 있습니다:

 

  • 프런트엔드 개발자는 Python 또는 Django에 대한 지식이나 경험이 없어도 됩니다.
  • 프론트엔드 개발자는 별도의 리포지토리/코드베이스에서 작업할 수 있어 협업 마찰이 줄어듭니다.
  • 프론트엔드 애플리케이션은 백엔드와 독립적으로 업데이트 및 배포 가능 - 사용자 대면 구성 요소는 종종 더 빈번한 업데이트 및 반복이 필요합니다.
  • 백엔드 비즈니스 로직에서 인터페이스 및 디스플레이 분리
  • 모바일 앱과 같은 다른 프론트엔드/클라이언트에서도 동일한 백엔드 API를 사용할 수 있습니다.
  • 더 이상 템플릿을 렌더링할 필요가 없고 응답 데이터를 적게 반환하므로 웹 서버의 부하 감소
  • 더욱 반응적이고 동적인 사용자 경험

 

성능 문제를 해결하는 방법

여기서는 모놀리식 애플리케이션의 활용도가 증가함에 따라 성능 확장성에 대한 우려를 해결할 수 있는 몇 가지 효과적인 방법을 살펴보겠습니다.

캐시 추가

애플리케이션 성능이 문제가 되는 경우, 적절한 캐싱을 구현하여 웹 서버와 데이터베이스의 부하와 혼잡(두 가지 일반적인 성능 병목 현상)을 크게 줄일 수 있습니다. Django에는 사이트별, 뷰별 또는 보다 세분화된 캐싱을 지원하는 캐싱 프레임워크가 기본 제공됩니다. 또한 데이터베이스 수준 캐싱을 제공하는 타사 프로젝트(예: django-cachalot, django-cache-machine 및 django-cacheops)도 있습니다.

애플리케이션에 적합한 캐싱 수준은 애플리케이션의 성격에 따라 달라지며, 자주 업데이트되는 데이터를 표시하는 뷰를 캐싱하는 것은 문제가 될 수 있습니다.

 

"컴퓨터 과학에서 어려운 것은 캐시 무효화와 이름 지정, 단 두 가지뿐입니다." - Phil Karlton

 

데이터베이스 액세스 최적화

애플리케이션이 데이터베이스와 상호 작용하는 방식을 현명하게 파악하면 성능에 큰 영향을 미칠 수 있습니다. 다음은 몇 가지 팁입니다:

  • 액세스 하려는 관련 모델이 있는 쿼리 집합에 .select_related() 또는 .prefetch_related()를 사용합니다. 이렇게 하면 관련 모델당 하나의 쿼리가 아닌 단일 쿼리로 모든 관련 모델의 데이터를 수집합니다.
  • .update() 쿼리 집합 메서드를 사용하여 모델을 선택하고 필드 값을 변경한 다음 각각을 개별적으로 저장하는 대신 대량 SQL 업데이트를 수행합니다(단일 모델의 필드 값 업데이트에도 적용됨).
  • 특정 필드 값을 기반으로 모델 항목이 존재하는지 여부를 여러 번 확인해야 하거나, 한 값을 다른 값으로 변환하는 조회 테이블로 효과적으로 작동하는 모델이 있다고 가정해 보겠습니다. 확인하거나 조회하려는 모든 값에 대해 별도의 쿼리를 수행하는 대신 .values_list()를 사용하여 단일 쿼리에서 모든 데이터를 선택하여 필요한 필드만 선택하면 됩니다. 그런 다음 해당 데이터를 사용하여 메모리에서 집합(존재 여부 확인용) 또는 딕셔너리(값 조회용)를 구성하고 이를 대신 사용하세요.
  • 자주 액세스하는 큰 테이블에는 합계 또는 카운트와 같은 집계 함수를 사용하지 마세요. 이렇게 하면 집계 메트릭이 계산되는 동안 전체 테이블이 잠겨 다른 쿼리가 테이블에 액세스하지 못하고 경우에 따라 교착 상태가 발생할 수 있습니다. 다른 대안은 새 레코드가 추가될 때마다 업데이트되는 다른 테이블에 합계 또는 카운트 값을 유지하는 것입니다.

여러 데이터베이스 사용

데이터베이스는 바쁜 소프트웨어 애플리케이션에서 성능 병목 현상이 발생하는 것으로 악명이 높습니다. 캐싱과 위의 최적화를 구현한 후에도 여전히 문제가 발생한다면 부하를 분산할 수 있는 방법이 있습니다.

애플리케이션 모듈이 동일한 장고 프로젝트에 있다고 해서 모두 동일한 데이터베이스를 사용해야 하는 것은 아닙니다. 장고는 데이터베이스 라우팅을 지원하며, 앱별로 특정 데이터베이스로 쿼리를 보내는 데이터베이스 라우터 클래스를 정의하는 것은 간단합니다. 이렇게 하면 데이터베이스 부하를 각 모듈에 대해 독립적으로 분할하고 확장할 수 있습니다. 한 가지 주의할 점은 서로 다른 데이터베이스에 있는 모듈 간에 모델 관계를 가질 수 없다는 것입니다.

데이터베이스에 중복성 또는 고가용성을 위한 복제 구성이 있는 경우 기본 데이터베이스가 쓰기에 사용되는 동안 읽기는 복제본으로 향하고 쓰기는 복제본으로 향하는 데이터베이스 라우터를 만들 수 있습니다. 일반적으로 읽기 작업이 쓰기 작업보다 더 일반적이므로 이렇게 하면 기본 데이터베이스의 부하를 크게 줄일 수 있습니다. 다음은 이 구성을 지원하는 프로젝트입니다.

읽기 복제본이 있는 데이터베이스 라우팅 아키텍처(출처)

 

웹 서버 추가

웹 서버가 성능 병목 현상이 발생하는 경우 웹 서버를 추가하여 수평적으로 확장할 수 있습니다. 대부분의 경우 로드 밸런서를 구성하여 트래픽을 서버 간에 균등하게 분산하는 것이 적합합니다.

애플리케이션 내의 여러 모듈에 대한 로드 프로필이 크게 다른 경우 웹 서버 모음을 만들고 URL 접두사에 따라 특정 서버로 라우팅하도록 로드 밸런서를 구성할 수 있습니다. 이렇게 하면 각 모듈에 대해 웹 서버 하드웨어 요구 사항을 독립적으로 확장할 수 있습니다.

결론

가장 중요한 점은 소프트웨어 아키텍처에 대한 정답은 없으며 애플리케이션, 비즈니스, 개발팀의 특성에 따라 가장 적합한 솔루션이 달라질 수 있다는 것입니다. 직면할 수 있는 문제를 해결하는 방법은 여러 가지가 있을 수 있으며, 올바른 선택을 하려면 관련된 장단점에 대한 충분한 이해가 필요합니다.

이 글을 재미있게 읽으셨기를 바라며, 그 과정에서 무언가를 배웠을 수도 있습니다! 그렇다면 다른 글도 확인해 보시고 앞으로의 글도 팔로우해 주세요.