[{"data":1,"prerenderedAt":999},["ShallowReactive",2],{"post-document:\u002Fbackend\u002Fmsa\u002F2026\u002F08\u002F26\u002FMSA1\u002F":3},{"id":4,"title":5,"body":6,"categories":980,"date":982,"description":983,"extension":984,"image":985,"key_concepts":985,"last_modified_at":985,"legacyPath":986,"meta":987,"navigation":393,"part":985,"path":989,"published":393,"robots":985,"seo":990,"series":985,"stem":991,"strengths":985,"summary":985,"tags":992,"tradeoffs":985,"__hash__":998},"posts\u002Fposts\u002FBackend\u002FMSA\u002FMSA.md","MSA, 서비스 분리와 운영의 원리",{"type":7,"value":8,"toc":953},"minimark",[9,13,17,28,31,34,39,44,47,53,56,62,65,69,72,78,81,87,90,96,99,103,109,112,114,118,121,135,138,142,145,151,169,214,250,253,257,260,266,323,326,345,348,350,354,358,361,367,370,374,377,420,423,429,432,438,441,445,448,454,457,546,549,555,558,560,564,568,571,577,580,586,589,593,596,602,605,608,612,615,621,624,626,630,633,683,689,692,695,701,703,707,710,824,828,831,837,840,865,868,874,877,880,884,887,907,910,916,919,925,929,932,940,946,949],[10,11,5],"h1",{"id":12},"msa-서비스-분리와-운영의-원리",[14,15,16],"p",{},"MSA(Microservices Architecture)는 하나의 큰 애플리케이션을 작고 독립적인 서비스들로 나누어 개발하고 운영하는 아키텍처다. 핵심은 코드를 폴더별로 분리하는 데 있지 않다. 각 서비스가 자신의 비즈니스 책임, 실행 환경, 배포 주기, 데이터의 소유권을 독립적으로 갖도록 만드는 것이 목적이다.",[18,19,25],"pre",{"className":20,"code":22,"language":23,"meta":24},[21],"language-text","모놀리식\n\n┌──────────────────────────────┐\n│         Application          │\n│                              │\n│  User, Order, Payment,       │\n│  Product, Delivery           │\n│                              │\n└──────────────────────────────┘\n               │\n               ▼\n          하나의 배포\n\nMSA\n\n┌────────┐  ┌────────┐  ┌──────────┐\n│  User  │  │ Order  │  │ Payment  │\n└────────┘  └────────┘  └──────────┘\n     │            │            │\n 독립 배포      독립 배포      독립 배포\n","text","",[26,27,22],"code",{"__ignoreMap":24},[14,29,30],{},"처음부터 MSA가 정답은 아니다. 기능과 팀 규모가 작은 단계에서는 모놀리식이 개발과 배포가 더 단순하다. 하지만 서비스가 커지고 변경, 배포, 확장, 장애의 영향 범위를 줄여야 할 때 MSA가 강점을 보인다.",[32,33],"hr",{},[35,36,38],"h2",{"id":37},"_1-서비스-경계와-독립성","1. 서비스 경계와 독립성",[40,41,43],"h3",{"id":42},"msa가-해결하려는-문제","MSA가 해결하려는 문제",[14,45,46],{},"모놀리식에서 결제 기능만 수정해도 하나의 애플리케이션 전체를 빌드, 테스트, 배포해야 할 수 있다.",[18,48,51],{"className":49,"code":50,"language":23,"meta":24},[21],"결제 코드 수정\n    ↓\n전체 애플리케이션 빌드와 테스트\n    ↓\n전체 애플리케이션 배포\n    ↓\n주문, 회원, 배송 기능에도 영향 가능\n",[26,52,50],{"__ignoreMap":24},[14,54,55],{},"MSA에서는 결제 서비스만 변경하고 배포할 수 있다.",[18,57,60],{"className":58,"code":59,"language":23,"meta":24},[21],"Payment Service 수정\n    ↓\nPayment만 빌드와 테스트\n    ↓\nPayment만 배포\n",[26,61,59],{"__ignoreMap":24},[14,63,64],{},"따라서 MSA의 중요한 목표는 변경의 영향 범위를 작게 만들고, 장애와 확장의 단위를 서비스별로 분리하는 것이다.",[40,66,68],{"id":67},"서비스는-기술-계층이-아니라-비즈니스-책임으로-나눈다","서비스는 기술 계층이 아니라 비즈니스 책임으로 나눈다",[14,70,71],{},"다음처럼 기술 계층을 기준으로 서비스를 나누는 것은 좋은 MSA 분리가 아니다.",[18,73,76],{"className":74,"code":75,"language":23,"meta":24},[21],"Controller Service\nRepository Service\nUtility Service\n",[26,77,75],{"__ignoreMap":24},[14,79,80],{},"이 구조에서는 하나의 비즈니스 기능을 처리할 때 여러 서비스를 반드시 거쳐야 하므로 오히려 결합도가 높아진다. 대신 업무 책임을 기준으로 나눈다.",[18,82,85],{"className":83,"code":84,"language":23,"meta":24},[21],"User Service       회원 관리\nOrder Service      주문 생성, 상태 관리, 취소, 조회\nPayment Service    결제 요청, 승인, 취소, 환불\nInventory Service  재고 관리\nDelivery Service   배송 생성과 상태 관리\n",[26,86,84],{"__ignoreMap":24},[14,88,89],{},"이처럼 한 서비스가 책임지는 업무 범위를 Bounded Context라고 한다. 예를 들어 주문 서비스는 주문 상태를 관리하지만 결제 승인 내부 로직이나 결제 테이블을 직접 다루지 않는다.",[18,91,94],{"className":92,"code":93,"language":23,"meta":24},[21],"나쁜 구조\n\nOrder Service ─────> Payment DB 직접 접근\n\n좋은 구조\n\nOrder Service ── API 또는 Event ──> Payment Service ──> Payment DB\n",[26,95,93],{"__ignoreMap":24},[14,97,98],{},"각 데이터의 주인은 그 데이터를 담당하는 서비스다. 다른 서비스는 공개된 API나 이벤트를 통해 필요한 정보를 얻어야 한다.",[40,100,102],{"id":101},"기본적인-쇼핑몰-구조","기본적인 쇼핑몰 구조",[18,104,107],{"className":105,"code":106,"language":23,"meta":24},[21],"                        Client\n                          │\n                          ▼\n                   ┌─────────────┐\n                   │ API Gateway │\n                   └──────┬──────┘\n                          │\n          ┌───────────────┼─────────────────┐\n          ▼               ▼                 ▼\n     User Service    Order Service    Product Service\n          │               │                 │\n          ▼               ▼                 ▼\n       User DB         Order DB          Product DB\n                          │\n                          ▼\n                   Payment Service\n                          │\n                          ▼\n                      Payment DB\n",[26,108,106],{"__ignoreMap":24},[14,110,111],{},"각 서비스는 보통 별도의 애플리케이션 프로세스이며, 독립적으로 빌드하고 배포할 수 있다.",[32,113],{},[35,115,117],{"id":116},"_2-서비스-통신과-데이터-소유권","2. 서비스 통신과 데이터 소유권",[14,119,120],{},"모놀리식에서는 같은 프로세스 안에서 메서드를 호출할 수 있다.",[18,122,126],{"className":123,"code":124,"language":125,"meta":24,"style":24},"language-java shiki shiki-themes github-light github-dark","paymentService.pay(order);\n","java",[26,127,128],{"__ignoreMap":24},[129,130,133],"span",{"class":131,"line":132},"line",1,[129,134,124],{},[14,136,137],{},"MSA에서는 주문과 결제 서비스가 서로 다른 프로세스에 있으므로 네트워크를 통해 통신해야 한다. 대표적인 선택지는 동기 통신과 비동기 통신이다.",[40,139,141],{"id":140},"동기-통신-즉시-응답이-필요할-때","동기 통신: 즉시 응답이 필요할 때",[14,143,144],{},"주문 서비스가 결제를 요청하고 그 결과를 바로 받아야 한다면 REST나 gRPC 같은 동기 통신을 쓸 수 있다.",[18,146,149],{"className":147,"code":148,"language":23,"meta":24},[21],"Order Service\n      │\n      │ HTTP POST \u002Fpayments\n      ▼\nPayment Service\n      │\n      │ Response\n      ▼\nOrder Service\n",[26,150,148],{"__ignoreMap":24},[18,152,156],{"className":153,"code":154,"language":155,"meta":24,"style":24},"language-http shiki shiki-themes github-light github-dark","POST \u002Fpayments\nContent-Type: application\u002Fjson\n","http",[26,157,158,163],{"__ignoreMap":24},[129,159,160],{"class":131,"line":132},[129,161,162],{},"POST \u002Fpayments\n",[129,164,166],{"class":131,"line":165},2,[129,167,168],{},"Content-Type: application\u002Fjson\n",[18,170,174],{"className":171,"code":172,"language":173,"meta":24,"style":24},"language-json shiki shiki-themes github-light github-dark","{\n  \"orderId\": 1001,\n  \"price\": 50000\n}\n","json",[26,175,176,182,197,208],{"__ignoreMap":24},[129,177,178],{"class":131,"line":132},[129,179,181],{"class":180},"sVt8B","{\n",[129,183,184,188,191,194],{"class":131,"line":165},[129,185,187],{"class":186},"sj4cs","  \"orderId\"",[129,189,190],{"class":180},": ",[129,192,193],{"class":186},"1001",[129,195,196],{"class":180},",\n",[129,198,200,203,205],{"class":131,"line":199},3,[129,201,202],{"class":186},"  \"price\"",[129,204,190],{"class":180},[129,206,207],{"class":186},"50000\n",[129,209,211],{"class":131,"line":210},4,[129,212,213],{"class":180},"}\n",[18,215,217],{"className":171,"code":216,"language":173,"meta":24,"style":24},"{\n  \"paymentId\": 771,\n  \"status\": \"SUCCESS\"\n}\n",[26,218,219,223,235,246],{"__ignoreMap":24},[129,220,221],{"class":131,"line":132},[129,222,181],{"class":180},[129,224,225,228,230,233],{"class":131,"line":165},[129,226,227],{"class":186},"  \"paymentId\"",[129,229,190],{"class":180},[129,231,232],{"class":186},"771",[129,234,196],{"class":180},[129,236,237,240,242],{"class":131,"line":199},[129,238,239],{"class":186},"  \"status\"",[129,241,190],{"class":180},[129,243,245],{"class":244},"sZZnC","\"SUCCESS\"\n",[129,247,248],{"class":131,"line":210},[129,249,213],{"class":180},[14,251,252],{},"동기 통신은 흐름이 직관적이고 결과를 즉시 확인할 수 있다는 장점이 있다. 반면 결제 서비스가 느리거나 장애 상태이면 주문 서비스도 응답을 기다리게 된다. 즉, 서비스 사이에 시간적 의존성이 생긴다.",[40,254,256],{"id":255},"비동기-통신-결합도를-낮추고-확장할-때","비동기 통신: 결합도를 낮추고 확장할 때",[14,258,259],{},"주문이 생성됐다는 사실만 전달하고, 이후 처리는 각 서비스가 알아서 수행해도 된다면 이벤트 기반 통신을 사용할 수 있다.",[18,261,264],{"className":262,"code":263,"language":23,"meta":24},[21],"Order Service\n      │\n      │ OrderCreated 이벤트 발행\n      ▼\n    Message Broker\n      │\n ┌────┼─────────────┐\n ▼    ▼             ▼\nPayment Delivery Analytics\n",[26,265,263],{"__ignoreMap":24},[18,267,269],{"className":171,"code":268,"language":173,"meta":24,"style":24},"{\n  \"event\": \"OrderCreated\",\n  \"orderId\": 1001,\n  \"userId\": 17,\n  \"price\": 50000\n}\n",[26,270,271,275,287,297,309,318],{"__ignoreMap":24},[129,272,273],{"class":131,"line":132},[129,274,181],{"class":180},[129,276,277,280,282,285],{"class":131,"line":165},[129,278,279],{"class":186},"  \"event\"",[129,281,190],{"class":180},[129,283,284],{"class":244},"\"OrderCreated\"",[129,286,196],{"class":180},[129,288,289,291,293,295],{"class":131,"line":199},[129,290,187],{"class":186},[129,292,190],{"class":180},[129,294,193],{"class":186},[129,296,196],{"class":180},[129,298,299,302,304,307],{"class":131,"line":210},[129,300,301],{"class":186},"  \"userId\"",[129,303,190],{"class":180},[129,305,306],{"class":186},"17",[129,308,196],{"class":180},[129,310,312,314,316],{"class":131,"line":311},5,[129,313,202],{"class":186},[129,315,190],{"class":180},[129,317,207],{"class":186},[129,319,321],{"class":131,"line":320},6,[129,322,213],{"class":180},[14,324,325],{},"Order Service는 이벤트를 발행할 뿐이며, Payment나 Delivery가 어떻게 처리하는지 직접 알 필요가 없다. Kafka나 RabbitMQ 같은 메시지 브로커는 다음 효과를 제공한다.",[327,328,329,333,336,339,342],"ul",{},[330,331,332],"li",{},"결합도 감소: Producer와 Consumer가 서로를 직접 알 필요가 없다.",[330,334,335],{},"버퍼링: 소비자가 잠시 느려도 메시지를 보관할 수 있다.",[330,337,338],{},"팬아웃: 하나의 이벤트를 여러 소비자가 처리할 수 있다.",[330,340,341],{},"장애 격리: 소비자 하나의 장애가 발행자를 즉시 멈추게 하지 않는다.",[330,343,344],{},"재처리: 저장된 이벤트를 다시 읽을 수 있다.",[14,346,347],{},"MSA가 곧 Kafka라는 뜻은 아니다. Kafka는 서비스 간 결합도를 낮추기 위한 여러 선택지 중 하나다.",[32,349],{},[35,351,353],{"id":352},"_3-분산-데이터와-장애-대응","3. 분산 데이터와 장애 대응",[40,355,357],{"id":356},"database-per-service","Database per Service",[14,359,360],{},"MSA에서는 보통 서비스가 자신의 데이터를 직접 소유한다.",[18,362,365],{"className":363,"code":364,"language":23,"meta":24},[21],"User Service    ──> User DB\nOrder Service   ──> Order DB\nPayment Service ──> Payment DB\n",[26,366,364],{"__ignoreMap":24},[14,368,369],{},"Order Service가 Payment DB의 테이블을 직접 수정하면 서비스 경계가 무너진다. 결제 정보가 필요하면 Payment API를 호출하거나 Payment가 발행한 이벤트를 소비해야 한다.",[40,371,373],{"id":372},"분산-트랜잭션과-saga","분산 트랜잭션과 Saga",[14,375,376],{},"하나의 데이터베이스를 쓰는 모놀리식에서는 여러 작업을 하나의 트랜잭션으로 묶을 수 있다.",[18,378,382],{"className":379,"code":380,"language":381,"meta":24,"style":24},"language-sql shiki shiki-themes github-light github-dark","BEGIN;\n\nINSERT INTO orders (...);\nINSERT INTO payments (...);\nUPDATE inventory SET quantity = quantity - 1 WHERE product_id = ...;\n\nCOMMIT;\n","sql",[26,383,384,389,395,400,405,410,414],{"__ignoreMap":24},[129,385,386],{"class":131,"line":132},[129,387,388],{},"BEGIN;\n",[129,390,391],{"class":131,"line":165},[129,392,394],{"emptyLinePlaceholder":393},true,"\n",[129,396,397],{"class":131,"line":199},[129,398,399],{},"INSERT INTO orders (...);\n",[129,401,402],{"class":131,"line":210},[129,403,404],{},"INSERT INTO payments (...);\n",[129,406,407],{"class":131,"line":311},[129,408,409],{},"UPDATE inventory SET quantity = quantity - 1 WHERE product_id = ...;\n",[129,411,412],{"class":131,"line":320},[129,413,394],{"emptyLinePlaceholder":393},[129,415,417],{"class":131,"line":416},7,[129,418,419],{},"COMMIT;\n",[14,421,422],{},"작업 중 하나가 실패하면 ROLLBACK으로 전체를 되돌릴 수 있다. 하지만 MSA에서는 주문, 결제, 재고가 서로 다른 데이터베이스에 있어 하나의 로컬 트랜잭션으로 묶기 어렵다.",[18,424,427],{"className":425,"code":426,"language":23,"meta":24},[21],"주문 생성 성공\n    ↓\n결제 성공\n    ↓\n재고 차감 실패\n",[26,428,426],{"__ignoreMap":24},[14,430,431],{},"이때 Saga 패턴은 이미 성공한 작업을 데이터베이스 롤백이 아니라 비즈니스 보상 작업으로 되돌린다.",[18,433,436],{"className":434,"code":435,"language":23,"meta":24},[21],"재고 차감 실패\n    ↓\n결제 취소 또는 환불\n    ↓\n주문 취소\n",[26,437,435],{"__ignoreMap":24},[14,439,440],{},"Saga는 여러 로컬 트랜잭션과 보상 트랜잭션을 조합해 최종적인 일관성을 맞추는 방법이다. 중간 상태가 잠시 존재할 수 있으므로, 상태 전이와 실패 시나리오를 처음부터 설계해야 한다.",[40,442,444],{"id":443},"네트워크-실패는-정상적인-경우다","네트워크 실패는 정상적인 경우다",[14,446,447],{},"서비스 내부 메서드 호출과 달리 네트워크 호출은 언제든 실패할 수 있다.",[18,449,452],{"className":450,"code":451,"language":23,"meta":24},[21],"Timeout\n서비스 다운\nDNS 또는 로드 밸런서 문제\n메시지 브로커 장애\n일시적인 네트워크 오류\n",[26,453,451],{"__ignoreMap":24},[14,455,456],{},"따라서 MSA에서는 다음 장치들을 조합해 장애 전파를 줄인다.",[458,459,460,476],"table",{},[461,462,463],"thead",{},[464,465,466,470,473],"tr",{},[467,468,469],"th",{},"기법",[467,471,472],{},"역할",[467,474,475],{},"주의점",[477,478,479,491,502,513,524,535],"tbody",{},[464,480,481,485,488],{},[482,483,484],"td",{},"Timeout",[482,486,487],{},"정해진 시간 뒤 대기를 멈춘다",[482,489,490],{},"너무 길면 스레드와 연결이 고갈된다",[464,492,493,496,499],{},[482,494,495],{},"Retry",[482,497,498],{},"일시적인 오류를 재시도한다",[482,500,501],{},"중복 요청을 고려해야 한다",[464,503,504,507,510],{},[482,505,506],{},"Idempotency",[482,508,509],{},"같은 요청을 여러 번 처리해도 결과를 같게 만든다",[482,511,512],{},"고유 요청 ID가 필요하다",[464,514,515,518,521],{},[482,516,517],{},"Circuit Breaker",[482,519,520],{},"반복 실패한 서비스 호출을 잠시 차단한다",[482,522,523],{},"복구 확인을 위한 반열림 상태가 필요하다",[464,525,526,529,532],{},[482,527,528],{},"Bulkhead",[482,530,531],{},"리소스를 분리해 한 기능의 고갈이 전체로 퍼지는 것을 막는다",[482,533,534],{},"자원 한도를 정해야 한다",[464,536,537,540,543],{},[482,538,539],{},"Message Queue",[482,541,542],{},"처리량 급증과 일시 장애를 완충한다",[482,544,545],{},"지연 처리와 재시도 정책이 필요하다",[14,547,548],{},"예를 들어 결제 요청에는 고유한 요청 ID를 두어 재시도해도 중복 결제가 생기지 않게 한다.",[18,550,553],{"className":551,"code":552,"language":23,"meta":24},[21],"첫 요청: paymentRequestId = abc123, 결제 성공\n재시도: paymentRequestId = abc123, 이미 처리한 결과 반환\n",[26,554,552],{"__ignoreMap":24},[14,556,557],{},"이 장치들이 없으면 결제 장애가 주문 서비스의 대기 증가로 이어지고, 주문의 스레드와 연결이 고갈된 뒤 게이트웨이까지 영향을 받는 연쇄 장애가 발생할 수 있다.",[32,559],{},[35,561,563],{"id":562},"_4-독립-확장과-실행-플랫폼","4. 독립 확장과 실행 플랫폼",[40,565,567],{"id":566},"필요한-서비스만-확장하기","필요한 서비스만 확장하기",[14,569,570],{},"서비스별 트래픽이 다르면 MSA는 필요한 서비스만 수평 확장할 수 있다.",[18,572,575],{"className":573,"code":574,"language":23,"meta":24},[21],"요청량\nUser       1,000 requests\u002Fsec\nOrder      5,000 requests\u002Fsec\nProduct   50,000 requests\u002Fsec\nPayment    3,000 requests\u002Fsec\n\n확장 예시\nUser Service      2 instances\nOrder Service     5 instances\nProduct Service  30 instances\nPayment Service   4 instances\n",[26,576,574],{"__ignoreMap":24},[14,578,579],{},"예를 들어 Product Service 인스턴스 한 대가 초당 1,000개 요청을 처리하고 초당 10,000개 요청을 받아야 한다면, 단순 계산으로 약 10개 인스턴스가 필요하다.",[18,581,584],{"className":582,"code":583,"language":23,"meta":24},[21],"필요 인스턴스 수 = 목표 처리량 \u002F 인스턴스당 처리량\n                  = 10,000 \u002F 1,000\n                  = 10\n",[26,585,583],{"__ignoreMap":24},[14,587,588],{},"이처럼 인스턴스를 늘려 처리 능력을 키우는 방식을 수평 확장이라고 한다.",[40,590,592],{"id":591},"docker와-kubernetes","Docker와 Kubernetes",[14,594,595],{},"서비스 수가 늘어나면 Java 버전, 라이브러리, 환경 변수, 실행 방법을 일관되게 관리하기 어려워진다. Docker는 애플리케이션과 실행 환경을 이미지로 묶어 배포 단위를 표준화한다.",[18,597,600],{"className":598,"code":599,"language":23,"meta":24},[21],"Order Service + JDK + Library + Runtime\n                    ↓\n              Docker Image\n                    ↓\n             Order Container\n",[26,601,599],{"__ignoreMap":24},[14,603,604],{},"컨테이너가 수백 개 이상으로 늘어나면 Kubernetes 같은 오케스트레이션 도구가 배포, 재시작, 확장, 서비스 발견, 로드 밸런싱, 롤링 업데이트, 헬스 체크를 자동화한다.",[14,606,607],{},"Docker나 Kubernetes를 쓴다고 MSA가 되는 것은 아니다. 이 도구들은 독립적인 서비스를 운영하기 쉽게 만들어 주는 운영 도구다.",[40,609,611],{"id":610},"api-gateway와-service-discovery","API Gateway와 Service Discovery",[14,613,614],{},"클라이언트가 수십 개 서비스의 주소를 모두 알아야 하면 사용과 변경 관리가 어렵다. API Gateway는 외부 요청의 단일 진입점이 되어 라우팅, 인증, 인가, 속도 제한, 로깅, TLS 종료를 담당한다.",[18,616,619],{"className":617,"code":618,"language":23,"meta":24},[21],"                 Client\n                   │\n                   ▼\n             API Gateway\n              \u002F    |    \\\n             ▼     ▼     ▼\n           User  Order Payment\n",[26,620,618],{"__ignoreMap":24},[14,622,623],{},"컨테이너 환경에서는 인스턴스의 IP가 계속 바뀔 수 있다. Service Discovery는 payment-service 같은 논리적 이름으로 현재 정상인 인스턴스를 찾게 해 준다.",[32,625],{},[35,627,629],{"id":628},"_5-관측-가능성과-시스템-전체-흐름","5. 관측 가능성과 시스템 전체 흐름",[14,631,632],{},"MSA에서는 요청 하나가 여러 서비스를 지나고 로그도 여러 곳에 흩어진다. 따라서 운영을 위해 로그, 메트릭, 트레이스를 함께 수집해야 한다.",[458,634,635,648],{},[461,636,637],{},[464,638,639,642,645],{},[467,640,641],{},"신호",[467,643,644],{},"무엇을 보는가",[467,646,647],{},"예시",[477,649,650,661,672],{},[464,651,652,655,658],{},[482,653,654],{},"Logs",[482,656,657],{},"개별 사건의 상세 기록",[482,659,660],{},"결제 실패, 주문 ID 1001",[464,662,663,666,669],{},[482,664,665],{},"Metrics",[482,667,668],{},"시간에 따른 수치",[482,670,671],{},"CPU 80%, 오류율 3%, 지연 시간 120ms",[464,673,674,677,680],{},[482,675,676],{},"Traces",[482,678,679],{},"요청이 지나간 전체 경로",[482,681,682],{},"Gateway -> Order -> Payment -> DB",[18,684,687],{"className":685,"code":686,"language":23,"meta":24},[21],"요청 ID: abc123\n\nGateway   10ms\n    ↓\nOrder     25ms\n    ↓\nPayment  300ms\n    ↓\nDB        50ms\n",[26,688,686],{"__ignoreMap":24},[14,690,691],{},"트레이스를 보면 Payment 구간이 병목이라는 사실을 빠르게 찾을 수 있다.",[14,693,694],{},"전체 구조를 단순화하면 다음과 같다.",[18,696,699],{"className":697,"code":698,"language":23,"meta":24},[21],"                        Client\n                          │\n                          ▼\n                    API Gateway\n                          │\n             ┌────────────┼────────────┐\n             ▼            ▼            ▼\n           User         Order       Product\n             │            │            │\n             ▼            ▼            ▼\n           DB           DB           DB\n                          │\n                          ▼\n                    Message Broker\n                    \u002F      |       \\\n                   ▼       ▼        ▼\n              Payment   Delivery  Analytics\n                 │          │\n                 ▼          ▼\n                 DB         DB\n\n운영 계층: Docker, Kubernetes\n관측 계층: Logs, Metrics, Traces\n",[26,700,698],{"__ignoreMap":24},[32,702],{},[35,704,706],{"id":705},"_6-msa를-선택하기-전에","6. MSA를 선택하기 전에",[14,708,709],{},"MSA는 배포와 확장, 장애 격리의 단위를 작게 만들 수 있지만 네트워크 지연, 부분 실패, 분산 트랜잭션, 운영 복잡성도 함께 가져온다. 서비스가 작다고 해서 항상 마이크로서비스가 되는 것은 아니다.",[458,711,712,728],{},[461,713,714],{},[464,715,716,719,722,725],{},[467,717,718],{},"구분",[467,720,721],{},"Monolith",[467,723,724],{},"Modular Monolith",[467,726,727],{},"MSA",[477,729,730,743,756,770,784,797,810],{},[464,731,732,735,738,740],{},[482,733,734],{},"애플리케이션과 프로세스",[482,736,737],{},"하나",[482,739,737],{},[482,741,742],{},"여러 개",[464,744,745,748,751,753],{},[482,746,747],{},"배포",[482,749,750],{},"한 번에 배포",[482,752,750],{},[482,754,755],{},"서비스별 독립 배포",[464,757,758,761,764,767],{},[482,759,760],{},"모듈 경계",[482,762,763],{},"약할 수 있음",[482,765,766],{},"강하게 설계 가능",[482,768,769],{},"네트워크 경계로 강제됨",[464,771,772,775,778,781],{},[482,773,774],{},"통신",[482,776,777],{},"함수 호출",[482,779,780],{},"함수와 인터페이스",[482,782,783],{},"네트워크와 메시지",[464,785,786,789,792,794],{},[482,787,788],{},"데이터베이스",[482,790,791],{},"보통 하나",[482,793,791],{},[482,795,796],{},"서비스별 소유",[464,798,799,802,805,807],{},[482,800,801],{},"확장",[482,803,804],{},"전체 확장",[482,806,804],{},[482,808,809],{},"서비스별 확장",[464,811,812,815,818,821],{},[482,813,814],{},"운영 난이도",[482,816,817],{},"낮음",[482,819,820],{},"중간",[482,822,823],{},"높음",[40,825,827],{"id":826},"msa가-만드는-비용","MSA가 만드는 비용",[14,829,830],{},"서비스를 분리하면 함수 호출은 네트워크 호출이 된다. 직렬화, 네트워크 전송, 라우팅, 역직렬화가 추가되므로 속도와 실패 가능성 모두 달라진다.",[18,832,835],{"className":833,"code":834,"language":23,"meta":24},[21],"Monolith: Function Call\n\nMSA: Serialization -> Network -> Routing -> Server -> Deserialization\n",[26,836,834],{"__ignoreMap":24},[14,838,839],{},"또한 데이터베이스가 분리되면 서비스 간 테이블을 직접 조인할 수 없다.",[18,841,843],{"className":379,"code":842,"language":381,"meta":24,"style":24},"-- 모놀리식에서는 하나의 DB에서 조인할 수 있다.\nSELECT *\nFROM orders o\nJOIN users u ON o.user_id = u.id;\n",[26,844,845,850,855,860],{"__ignoreMap":24},[129,846,847],{"class":131,"line":132},[129,848,849],{},"-- 모놀리식에서는 하나의 DB에서 조인할 수 있다.\n",[129,851,852],{"class":131,"line":165},[129,853,854],{},"SELECT *\n",[129,856,857],{"class":131,"line":199},[129,858,859],{},"FROM orders o\n",[129,861,862],{"class":131,"line":210},[129,863,864],{},"JOIN users u ON o.user_id = u.id;\n",[14,866,867],{},"MSA에서는 API 호출, 이벤트로 데이터 복제, CQRS 읽기 모델, 데이터 웨어하우스 같은 별도 설계가 필요하다. 이벤트가 전달되는 동안 서비스의 상태가 잠시 다를 수 있으며, 이를 최종 일관성이라고 한다.",[18,869,872],{"className":870,"code":871,"language":23,"meta":24},[21],"10:00:00.000  Order = PAID\n10:00:00.100  OrderCreated 또는 PaymentCompleted 이벤트 전달\n10:00:00.300  Delivery = READY\n",[26,873,871],{"__ignoreMap":24},[14,875,876],{},"즉, 모든 서비스의 상태가 언제나 즉시 같을 수는 없지만, 이벤트 처리가 끝나면 일관된 상태에 도달하도록 설계한다.",[14,878,879],{},"테스트와 운영의 비용도 증가한다. 서비스가 많아지면 단위 테스트뿐 아니라 통합 테스트, 계약 테스트, 종단 간 테스트가 필요하며, 저장소, CI\u002FCD 파이프라인, 배포, 로그, 모니터링 대상도 서비스 수만큼 늘어난다.",[40,881,883],{"id":882},"언제-msa가-적합한가","언제 MSA가 적합한가",[14,885,886],{},"다음 상황에서는 MSA의 이점이 비용보다 클 가능성이 높다.",[327,888,889,892,895,898,901,904],{},[330,890,891],{},"팀이 여러 개이고 도메인별 소유권이 명확하다.",[330,893,894],{},"서비스별 배포 주기나 기술 요구가 크게 다르다.",[330,896,897],{},"특정 서비스에만 매우 높은 트래픽이 발생해 독립 확장이 필요하다.",[330,899,900],{},"한 서비스의 장애가 전체 서비스로 번지지 않도록 강하게 격리해야 한다.",[330,902,903],{},"주문, 결제, 배송처럼 비즈니스 경계와 데이터 소유권이 충분히 명확하다.",[330,905,906],{},"자동화된 배포와 모니터링, 장애 대응을 운영할 역량이 있다.",[14,908,909],{},"반대로 팀과 서비스가 작고 도메인 경계가 계속 바뀌며 트래픽도 크지 않다면 MSA는 과한 설계가 될 수 있다.",[18,911,914],{"className":912,"code":913,"language":23,"meta":24},[21],"개발자 3명\n사용자 1,000명\n도메인 복잡도 낮음\n서비스 경계가 아직 불분명함\n",[26,915,913],{"__ignoreMap":24},[14,917,918],{},"이런 단계에서는 모듈 경계를 잘 지킨 Modular Monolith가 더 현실적인 출발점이다. 애플리케이션 내부에서 기능을 분리해 두고, 변경 주기, 트래픽, 조직 소유권이 분명해진 모듈부터 독립 서비스로 꺼내면 된다.",[18,920,923],{"className":921,"code":922,"language":23,"meta":24},[21],"Monolith\n    ↓\n모듈 경계 명확화\n    ↓\nModular Monolith\n    ↓\n분리 가치가 큰 모듈부터 추출\n    ↓\nMSA\n",[26,924,922],{"__ignoreMap":24},[40,926,928],{"id":927},"핵심-정리","핵심 정리",[14,930,931],{},"MSA를 한 문장으로 표현하면, 명확한 비즈니스 책임을 가진 서비스를 독립적으로 개발, 배포, 확장할 수 있도록 설계하는 아키텍처다.",[14,933,934,935,939],{},"더 본질적으로는 ",[936,937,938],"strong",{},"변경, 장애, 확장, 조직의 영향 범위를 서비스 경계 안으로 제한하는 방법","이다.",[18,941,944],{"className":942,"code":943,"language":23,"meta":24},[21],"Business Domain\n      ↓\n책임을 기준으로 분리\n      ↓\nOrder, Payment, Delivery\n      ↓\n각 서비스가 데이터와 배포를 독립적으로 소유\n      ↓\nAPI 또는 이벤트로 통신\n      ↓\nTimeout, Retry, Circuit Breaker로 장애를 격리\n      ↓\n필요한 서비스만 확장하고 관측한다\n",[26,945,943],{"__ignoreMap":24},[14,947,948],{},"MSA의 목적은 서비스를 많이 만드는 것이 아니라, 시스템과 조직이 감당할 수 있는 방식으로 변화와 운영을 독립시키는 데 있다.",[950,951,952],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":24,"searchDepth":165,"depth":165,"links":954},[955,960,964,969,974,975],{"id":37,"depth":165,"text":38,"children":956},[957,958,959],{"id":42,"depth":199,"text":43},{"id":67,"depth":199,"text":68},{"id":101,"depth":199,"text":102},{"id":116,"depth":165,"text":117,"children":961},[962,963],{"id":140,"depth":199,"text":141},{"id":255,"depth":199,"text":256},{"id":352,"depth":165,"text":353,"children":965},[966,967,968],{"id":356,"depth":199,"text":357},{"id":372,"depth":199,"text":373},{"id":443,"depth":199,"text":444},{"id":562,"depth":165,"text":563,"children":970},[971,972,973],{"id":566,"depth":199,"text":567},{"id":591,"depth":199,"text":592},{"id":610,"depth":199,"text":611},{"id":628,"depth":165,"text":629},{"id":705,"depth":165,"text":706,"children":976},[977,978,979],{"id":826,"depth":199,"text":827},{"id":882,"depth":199,"text":883},{"id":927,"depth":199,"text":928},[981,727],"Backend","2026-08-26 14:08:02 +0900","모놀리식에서 MSA로 전환할 때 알아야 할 서비스 경계, 통신, 데이터, 장애 대응, 운영 원칙을 정리한다.","md",null,"\u002Fbackend\u002Fmsa\u002F2026\u002F08\u002F26\u002FMSA1\u002F",{"layout":988},"post","\u002Fposts\u002Fbackend\u002Fmsa\u002Fmsa",{"title":5,"description":983},"posts\u002FBackend\u002FMSA\u002FMSA",[727,993,994,995,996,997],"Microservices","Kafka","Docker","Kubernetes","Saga","QVTOali3Ui_dakeBlopgVUT92MMpVVabUqwFtz5mxVI",1788744785467]