Strangler Fig Pattern
레거시 시스템을 점진적으로 교체하는 패턴
레거시 시스템을 점진적으로 교체하는 패턴
Strangler Fig Pattern은 레거시 시스템을 점진적으로 새 시스템으로 교체하는 마이그레이션 전략입니다. 호주의 교살무화과(Strangler Fig) 나무가 숙주 나무를 천천히 감싸 결국 대체하는 것에서 이름을 따왔습니다. Martin Fowler가 2004년 소개한 이후 모놀리스에서 마이크로서비스로의 전환에 널리 사용됩니다.
핵심 원리:
구현 단계:
Facade 구현 방식:
장점: 위험 분산(한 번에 조금씩), 롤백 용이, 비즈니스 연속성 유지, 팀 학습 곡선 완화. 단점: 두 시스템 동시 유지 비용, 복잡한 라우팅 로직, 장기 프로젝트화 위험.
# nginx.conf - Strangler Facade
upstream legacy_monolith {
server legacy-app:8080;
}
upstream new_order_service {
server order-service:8080;
}
upstream new_user_service {
server user-service:8080;
}
server {
listen 80;
server_name api.example.com;
# 새 마이크로서비스로 이전된 기능
location /api/v2/orders {
proxy_pass http://new_order_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/v2/users {
proxy_pass http://new_user_service;
proxy_set_header Host $host;
}
# 아직 이전되지 않은 기능 → 레거시로 전달
location /api/ {
proxy_pass http://legacy_monolith;
proxy_set_header Host $host;
}
# 단계적 전환: 10% 트래픽만 새 시스템으로
location /api/v2/products {
# 난수 기반 트래픽 분할
set $backend legacy_monolith;
if ($request_id ~* "[0-9]$") {
set $backend new_product_service;
}
proxy_pass http://$backend;
}
}
// strangler-facade.js
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
const app = express();
// Feature Flag 서비스 (LaunchDarkly, Unleash 등)
const featureFlags = {
'new-order-service': true,
'new-user-service': true,
'new-product-service': false, // 아직 이전 중
};
// 레거시 모놀리스
const legacyProxy = createProxyMiddleware({
target: 'http://legacy-monolith:8080',
changeOrigin: true,
});
// 새 마이크로서비스들
const orderServiceProxy = createProxyMiddleware({
target: 'http://order-service:8080',
changeOrigin: true,
pathRewrite: { '^/api/orders': '/orders' },
});
const userServiceProxy = createProxyMiddleware({
target: 'http://user-service:8080',
changeOrigin: true,
});
// Strangler Router 미들웨어
function stranglerRouter(featureFlag, newProxy) {
return (req, res, next) => {
if (featureFlags[featureFlag]) {
// 새 서비스로 라우팅
console.log(`[Strangler] Routing to new service: ${featureFlag}`);
return newProxy(req, res, next);
}
// 레거시로 폴백
console.log(`[Strangler] Fallback to legacy: ${featureFlag}`);
return legacyProxy(req, res, next);
};
}
// 라우팅 규칙
app.use('/api/orders', stranglerRouter('new-order-service', orderServiceProxy));
app.use('/api/users', stranglerRouter('new-user-service', userServiceProxy));
// 이전되지 않은 모든 요청 → 레거시
app.use('/api', legacyProxy);
// 점진적 롤아웃 (퍼센트 기반)
function canaryRouter(percentage, newProxy) {
return (req, res, next) => {
const userId = req.headers['x-user-id'] || req.ip;
const hash = hashCode(userId) % 100;
if (hash < percentage) {
return newProxy(req, res, next);
}
return legacyProxy(req, res, next);
};
}
// 20% 트래픽만 새 상품 서비스로
app.use('/api/products', canaryRouter(20, productServiceProxy));
app.listen(3000);
# istio-strangler.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: api-strangler
spec:
hosts:
- api.example.com
http:
# 완전히 이전된 기능
- match:
- uri:
prefix: /api/orders
route:
- destination:
host: order-service
port:
number: 8080
# 점진적 이전 중 (80% 레거시, 20% 새 서비스)
- match:
- uri:
prefix: /api/products
route:
- destination:
host: legacy-monolith
port:
number: 8080
weight: 80
- destination:
host: product-service
port:
number: 8080
weight: 20
# 특정 헤더로 새 서비스 테스트
- match:
- headers:
x-canary:
exact: "true"
uri:
prefix: /api/inventory
route:
- destination:
host: inventory-service
# 나머지 → 레거시
- route:
- destination:
host: legacy-monolith
port:
number: 8080
CTO: "10년 된 모놀리스를 마이크로서비스로 전환해야 하는데, Big Bang 리라이트는 리스크가 너무 커요."
아키텍트: "Strangler Fig Pattern으로 접근하죠. API Gateway를 앞에 두고, 기능별로 하나씩 새 서비스로 이전해요. 주문 도메인부터 시작해서 결제, 사용자 순으로요."
CTO: "기존 서비스는요?"
아키텍트: "그대로 운영하면서 점진적으로 트래픽을 이전해요. 문제 생기면 즉시 레거시로 롤백할 수 있고요. 1년 정도면 핵심 기능 이전 가능합니다."
면접관: "Strangler Fig Pattern의 단점은 뭐가 있나요?"
지원자: "첫째, 두 시스템을 동시에 운영해야 해서 인프라 비용과 운영 복잡도가 증가해요. 둘째, 데이터 일관성 유지가 어려워요. 레거시와 새 시스템이 같은 데이터를 참조할 때 동기화 문제가 생깁니다. 셋째, 프로젝트가 장기화되면 '영원한 마이그레이션' 상태에 빠질 수 있어요."
면접관: "데이터 동기화는 어떻게 해결하나요?"
지원자: "CDC(Change Data Capture)로 실시간 동기화하거나, 이벤트 소싱으로 양쪽에 이벤트를 발행하는 방법이 있어요. 또는 특정 기능 이전 시 관련 데이터도 함께 이전해서 의존성을 끊는 방법도 있습니다."
PM: "주문 서비스 이전 현황이 어떻게 돼요?"
개발자: "지금 30% 트래픽을 새 서비스로 보내고 있어요. 에러율 0.1% 미만이고, 응답 시간도 레거시보다 40% 빨라요. 이번 주에 50%로 올리고, 다음 주에 100% 전환 예정이에요."
PM: "레거시 코드는요?"
개발자: "100% 전환 후 2주 모니터링하고, 문제 없으면 레거시 주문 모듈 코드를 제거할 계획이에요. 그래야 진짜 '교살'이 완료되는 거죠."