들어가며
우리가 매일 사용하는 크롬 브라우저, 안드로이드 OS, 넷플릭스 앱은 수십억 줄의 코드로 이루어진 거대한 소프트웨어다. 이런 소프트웨어는 한 명의 천재가 만든 게 아니라 수천 명의 개발자가 동시에 협업하며 만들어 낸다.
과연 이렇게 거대한 소프트웨어는 어떻게 개발하고, 어떻게 배포하고, 어떻게 업데이트하며, 어떻게 장기적으로 유지할까?
이 글에서는 대규모 소프트웨어 개발의 핵심 원리를 아키텍처부터 업데이트 시스템 구현까지 전방위로 다룬다.
1. 거대 소프트웨어의 정의
어디서부터 "거대"인가?
일반적으로 다음 조건 중 하나 이상을 만족하면 대규모(Large-Scale) 소프트웨어로 분류한다.
| 기준 | 예시 |
|---|---|
| 코드 규모 | 수백만~수십억 줄 |
| 개발 인력 | 수백~수천 명 |
| 서비스 수 | 수백~수천 개의 마이크로서비스 |
| 사용자 규모 | 수억 명 이상 DAU |
| 배포 빈도 | 하루 수십~수백 회 |
실제 사례
- 구글: 단일 모노레포에 약 20억 줄 이상의 코드가 존재한다. 매일 수만 건의 커밋이 발생하고, 약 25,000명 이상의 개발자가 동시에 작업한다.
- 메타(Facebook): 단일 레포에서 수만 명의 엔지니어가 협업한다. 자체 빌드 시스템인 Buck을 사용하여 거대한 코드베이스를 효율적으로 빌드한다.
- 넷플릭스: 약 1,000개 이상의 마이크로서비스로 운영되며, 하루 수천 회 배포를 수행한다. Chaos Engineering의 선구자로 유명하다.
2. 모노레포 vs 멀티레포
대규모 소프트웨어를 관리하는 첫 번째 결정은 코드 저장소 전략이다.
모노레포 (Monorepo)
모든 프로젝트의 코드를 하나의 저장소에서 관리하는 방식이다.
company-repo/
frontend/
backend/
mobile/
infra/
shared-libs/
tools/
장점:
- 코드 공유가 쉽다 (공통 라이브러리를 즉시 참조)
- 원자적 커밋이 가능하다 (여러 프로젝트를 한 번에 변경)
- 리팩토링이 용이하다 (의존성을 한눈에 파악)
- 일관된 도구와 설정을 적용할 수 있다
단점:
- 저장소 크기가 거대해진다 (클론, 체크아웃 시간 증가)
- 빌드 시스템이 복잡해진다 (변경 영향 범위 분석 필요)
- 접근 제어가 어렵다 (팀별 권한 관리)
멀티레포 (Multi-repo)
각 프로젝트, 서비스, 라이브러리를 독립된 저장소로 관리하는 방식이다.
장점:
- 팀별 독립적인 개발 사이클
- 저장소 크기가 작아 빠른 클론
- 명확한 서비스 경계와 소유권
단점:
- 코드 공유가 번거롭다 (패키지 버전 관리 필요)
- 의존성 지옥 (Diamond Dependency 문제)
- 크로스 프로젝트 리팩토링이 어렵다
주요 기업의 선택
| 기업 | 전략 | 도구 |
|---|---|---|
| 구글 | 모노레포 | Piper (자체 VCS) + Bazel |
| 메타 | 모노레포 | Mercurial + Buck |
| 넷플릭스 | 멀티레포 | Gradle + Nebula |
| 마이크로소프트 | 모노레포 (일부) | VFS for Git (GVFS) |
| 아마존 | 멀티레포 | Brazil (자체 빌드 시스템) |
핵심은 "어떤 전략이 절대적으로 좋다"가 아니라, 조직의 규모와 문화에 맞는 전략을 선택하는 것이다.
3. 마이크로서비스 아키텍처
모놀리식에서 마이크로서비스로
초기 스타트업은 보통 하나의 애플리케이션(모놀리식)으로 시작한다. 하지만 규모가 커지면 다음과 같은 문제가 생긴다.
- 빌드 시간이 30분 이상 걸린다
- 하나의 버그가 전체 시스템을 다운시킨다
- 팀 간 코드 충돌이 빈번하다
- 특정 기능만 스케일 아웃이 불가능하다
이런 문제를 해결하기 위해 **마이크로서비스 아키텍처(MSA)**를 도입한다.
MSA의 핵심 구성 요소
API Gateway
모든 클라이언트 요청의 단일 진입점 역할을 한다. 인증, 라우팅, 속도 제한, 로깅 등을 처리한다.
# API Gateway 라우팅 예시
routes:
- path: /api/users
service: user-service
methods: [GET, POST]
- path: /api/orders
service: order-service
methods: [GET, POST, PUT]
- path: /api/payments
service: payment-service
methods: [POST]
서비스 메시 (Service Mesh)
서비스 간 통신을 관리하는 인프라 레이어다. Istio, Linkerd 같은 도구를 사용한다.
# Istio VirtualService 예시
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v2
weight: 90
- destination:
host: user-service
subset: v1
weight: 10
서비스 디스커버리
수백 개의 서비스가 서로를 찾을 수 있도록 하는 메커니즘이다. Consul, Eureka, Kubernetes DNS 등이 이 역할을 한다.
서킷 브레이커
특정 서비스에 장애가 발생했을 때 연쇄 장애(Cascade Failure)를 방지하는 패턴이다.
# 서킷 브레이커 의사 코드
class CircuitBreaker:
def __init__(self, threshold=5, timeout=30):
self.failure_count = 0
self.threshold = threshold
self.timeout = timeout
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
def call(self, func):
if self.state == "OPEN":
if self.timeout_expired():
self.state = "HALF_OPEN"
else:
raise CircuitOpenError("Service unavailable")
try:
result = func()
self.on_success()
return result
except Exception as e:
self.on_failure()
raise e
def on_failure(self):
self.failure_count += 1
if self.failure_count >= self.threshold:
self.state = "OPEN"
def on_success(self):
self.failure_count = 0
self.state = "CLOSED"
4. CI/CD 파이프라인
지속적 통합 (CI)
개발자가 코드를 푸시하면 자동으로 빌드, 테스트, 정적 분석이 실행된다.
# GitHub Actions CI 파이프라인 예시
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Unit Tests
run: npm run test:unit
- name: Integration Tests
run: npm run test:integration
- name: Build
run: npm run build
지속적 배포 (CD) - 배포 전략
카나리(Canary) 배포
새 버전을 전체 사용자의 일부(예: 1-5%)에게만 먼저 배포한다. 문제가 없으면 점진적으로 비율을 높인다.
트래픽 분배:
v1 (기존): 95% ████████████████████░
v2 (신규): 5% █░░░░░░░░░░░░░░░░░░░
30분 후 (이상 없음):
v1 (기존): 50% ██████████░░░░░░░░░░
v2 (신규): 50% ██████████░░░░░░░░░░
1시간 후 (이상 없음):
v1 (기존): 0% ░░░░░░░░░░░░░░░░░░░░
v2 (신규): 100% ████████████████████
블루-그린(Blue-Green) 배포
두 개의 동일한 환경(Blue, Green)을 유지하고, 트래픽을 한 번에 전환한다.
현재: Blue (v1) ← 트래픽
Green (v2) 대기 중
전환: Blue (v1) 대기
Green (v2) ← 트래픽
롤백: Blue (v1) ← 트래픽 (즉시 복구)
Green (v2) 대기
롤링(Rolling) 업데이트
인스턴스를 하나씩 순차적으로 교체한다. Kubernetes의 기본 배포 전략이다.
# Kubernetes Rolling Update 설정
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
spec:
containers:
- name: my-app
image: my-app:v2
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
5. 피처 플래그 (Feature Flags)
배포와 릴리스를 분리하라
피처 플래그는 코드를 배포하지만, 기능은 아직 활성화하지 않는 기법이다.
// 피처 플래그 사용 예시
const featureFlags = {
newCheckoutFlow: false,
darkMode: true,
aiRecommendation: false,
};
function renderCheckout() {
if (featureFlags.newCheckoutFlow) {
return renderNewCheckout();
}
return renderLegacyCheckout();
}
피처 플래그의 활용
| 활용 사례 | 설명 |
|---|---|
| 점진적 릴리스 | 1% - 10% - 50% - 100% 순으로 노출 |
| A/B 테스트 | 두 가지 UI를 비교 분석 |
| Kill Switch | 장애 발생 시 즉시 기능 비활성화 |
| 베타 테스트 | 특정 사용자 그룹에만 공개 |
| 지역별 릴리스 | 한국 먼저, 이후 글로벌 확대 |
피처 플래그 서비스 예시
# 피처 플래그 평가 엔진 (간략화)
class FeatureFlagService:
def is_enabled(self, flag_name, user_context):
flag = self.get_flag(flag_name)
if not flag.enabled:
return False
# 특정 사용자 화이트리스트
if user_context.user_id in flag.whitelist:
return True
# 퍼센티지 롤아웃
if flag.rollout_percentage > 0:
hash_value = hash(f"flag_name:{user_context.user_id}") % 100
return hash_value < flag.rollout_percentage
# 조건 기반 (국가, OS 등)
for rule in flag.rules:
if rule.matches(user_context):
return True
return False
주의: 피처 플래그가 쌓이면 기술 부채가 된다. 릴리스가 완료된 플래그는 반드시 정리해야 한다.
6. SW 업데이트 시스템 구현
이 섹션이 이 글의 핵심이다. 소프트웨어를 개발하는 것만큼 중요한 것이 사용자에게 업데이트를 안전하고 원활하게 전달하는 시스템이다.
6-1. 데스크톱 앱 업데이트
Electron auto-updater
Electron 기반 앱(VS Code, Slack Desktop, Discord 등)은 대부분 electron-updater를 사용한다.
// Electron auto-updater 구현
import { autoUpdater } from 'electron-updater';
// 업데이트 서버 설정
autoUpdater.setFeedURL({
provider: 'github',
owner: 'my-org',
repo: 'my-app',
});
// 업데이트 이벤트 핸들러
autoUpdater.on('checking-for-update', () => {
log.info('업데이트 확인 중...');
});
autoUpdater.on('update-available', (info) => {
log.info('새 버전 발견:', info.version);
// 사용자에게 알림 표시
showUpdateNotification(info);
});
autoUpdater.on('download-progress', (progress) => {
log.info('다운로드 진행률:', progress.percent.toFixed(1) + '%');
updateProgressBar(progress.percent);
});
autoUpdater.on('update-downloaded', (info) => {
// "업데이트를 위해 재시작" 다이얼로그 표시
showRestartDialog(info.version);
});
// 주기적 업데이트 확인 (4시간마다)
setInterval(() => {
autoUpdater.checkForUpdates();
}, 4 * 60 * 60 * 1000);
델타(Delta) 업데이트
전체 파일을 다시 다운로드하지 않고, 변경된 부분만 내려받는 방식이다.
전체 업데이트: v1.0 (200MB) → v1.1 (200MB) = 200MB 다운로드
델타 업데이트: v1.0 (200MB) → v1.1 (200MB) = 15MB 다운로드 (차이분만)
Chrome, VS Code 등이 이 방식을 사용한다. Courgette(Chrome)과 같은 바이너리 디핑 알고리즘이 핵심이다.
Squirrel 프레임워크
GitHub에서 만든 데스크톱 앱 업데이트 프레임워크다. 특징은 다음과 같다.
- 관리자 권한 없이 업데이트 가능
- 백그라운드 다운로드 및 설치
- 롤백 지원
- Windows, macOS 모두 지원
6-2. 모바일 앱 업데이트
앱스토어 기반 업데이트
일반적인 모바일 업데이트 흐름:
1. 개발자가 새 빌드를 스토어에 제출
2. 스토어 심사 (Apple: 24-48시간, Google: 수 시간)
3. 승인 후 스토어에 게시
4. 사용자 기기에서 자동/수동 업데이트
단계적 출시 (Staged Rollout)
Google Play의 단계적 출시 기능을 이용하면 전체 사용자의 일부에게만 먼저 배포할 수 있다.
Day 1: 1% 출시 → 크래시 리포트 모니터링
Day 2: 5% 출시 → 사용자 피드백 확인
Day 3: 20% 출시
Day 5: 50% 출시
Day 7: 100% 출시
OTA(Over-The-Air) 업데이트
앱스토어를 거치지 않고 JavaScript 번들을 직접 푸시하는 방식이다. React Native에서 CodePush(현재 EAS Update)를 통해 구현할 수 있다.
// React Native OTA 업데이트 (EAS Update 예시)
import * as Updates from 'expo-updates';
async function checkForOTAUpdate() {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
// 백그라운드에서 업데이트 다운로드
await Updates.fetchUpdateAsync();
// 사용자에게 재시작 확인
Alert.alert(
'업데이트 완료',
'새 버전이 준비되었습니다. 지금 적용하시겠습니까?',
[
{ text: '나중에', style: 'cancel' },
{
text: '지금 적용',
onPress: () => Updates.reloadAsync(),
},
]
);
}
} catch (error) {
console.error('OTA 업데이트 실패:', error);
}
}
주의: Apple의 가이드라인에서 OTA로 앱의 주요 기능이나 목적을 변경하는 것은 금지하고 있다. JavaScript 번들의 UI/로직 수정만 허용된다.
6-3. 서버 업데이트 (배포)
서버 소프트웨어의 업데이트는 앞서 다룬 CI/CD 배포 전략과 동일하다.
# ArgoCD를 이용한 GitOps 기반 서버 업데이트
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-service
spec:
project: default
source:
repoURL: https://github.com/my-org/k8s-manifests
path: services/my-service
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
GitOps 패턴을 사용하면 Git 저장소가 진실의 원천(Single Source of Truth)이 되어, Git에 커밋하면 자동으로 배포가 이루어진다.
6-4. "업데이트 후 재시작" 구현 패턴
많은 데스크톱 앱에서 볼 수 있는 "Update and Restart" 기능의 내부 동작을 살펴보자.
// "Update and Restart" 전체 흐름
// 1단계: 버전 체크 API
async function checkVersion(): Promise<UpdateInfo | null> {
const currentVersion = app.getVersion(); // "2.3.1"
const response = await fetch(
'https://update.example.com/api/check' +
'?app=myapp' +
'&version=' + currentVersion +
'&platform=' + process.platform +
'&arch=' + process.arch
);
const data = await response.json();
// data 예시:
// {
// "updateAvailable": true,
// "latestVersion": "2.4.0",
// "downloadUrl": "https://cdn.example.com/myapp-2.4.0-darwin-x64.zip",
// "checksum": "sha256:a1b2c3...",
// "releaseNotes": "버그 수정 및 성능 개선",
// "mandatory": false,
// "size": 52428800
// }
if (data.updateAvailable) {
return data;
}
return null;
}
// 2단계: 업데이트 다운로드 및 검증
async function downloadAndVerify(updateInfo: UpdateInfo): Promise<string> {
const tempDir = path.join(os.tmpdir(), 'app-update');
const downloadPath = path.join(tempDir, 'update.zip');
// 다운로드 (진행률 표시)
await downloadFile(updateInfo.downloadUrl, downloadPath, (progress) => {
mainWindow.setProgressBar(progress / 100);
mainWindow.webContents.send('update-progress', progress);
});
// 체크섬 검증
const fileHash = await calculateHash(downloadPath, 'sha256');
if (fileHash !== updateInfo.checksum.replace('sha256:', '')) {
throw new Error('체크섬 불일치: 파일이 손상되었거나 변조됨');
}
// 서명 검증 (코드 사이닝)
await verifySignature(downloadPath);
return downloadPath;
}
// 3단계: 업데이트 적용 및 재시작
function applyUpdateAndRestart(updatePath: string) {
// 현재 앱 경로 백업
const appDir = path.dirname(process.execPath);
const backupDir = appDir + '.backup';
// 업데이터 스크립트 생성 (앱 종료 후 실행)
const updaterScript = createUpdaterScript({
currentAppDir: appDir,
backupDir: backupDir,
updateArchive: updatePath,
executablePath: process.execPath,
});
// 업데이터를 별도 프로세스로 실행
const child = spawn(updaterScript, {
detached: true,
stdio: 'ignore',
});
child.unref();
// 현재 앱 종료
app.quit();
}
업데이터 스크립트가 수행하는 작업:
#!/bin/bash
# updater.sh - 앱 종료 후 실행되는 업데이트 스크립트
# 1. 기존 앱이 완전히 종료될 때까지 대기
sleep 2
while pgrep -f "myapp" > /dev/null; do
sleep 1
done
# 2. 기존 버전 백업
cp -r "/Applications/MyApp.app" "/Applications/MyApp.app.backup"
# 3. 새 버전 설치
unzip -o "/tmp/app-update/update.zip" -d "/Applications/"
# 4. 검증
if [ -f "/Applications/MyApp.app/Contents/MacOS/myapp" ]; then
# 성공: 백업 삭제 후 앱 재시작
rm -rf "/Applications/MyApp.app.backup"
open "/Applications/MyApp.app"
else
# 실패: 백업에서 복구
rm -rf "/Applications/MyApp.app"
mv "/Applications/MyApp.app.backup" "/Applications/MyApp.app"
open "/Applications/MyApp.app"
fi
# 5. 임시 파일 정리
rm -rf "/tmp/app-update"
핫스왑(Hot Swap) vs 콜드 업데이트
| 방식 | 설명 | 사용 예시 |
|---|---|---|
| 핫스왑 | 앱 재시작 없이 일부 모듈 교체 | Java 클래스 리로딩, Erlang 핫코드 로딩 |
| 웜 리스타트 | 프로세스만 재시작 (OS 재부팅 없음) | Node.js 서버 graceful restart |
| 콜드 업데이트 | 앱 완전 종료 후 재설치 | 데스크톱 앱, OS 업데이트 |
Erlang/OTP는 실행 중인 시스템의 코드를 중단 없이 교체할 수 있는 핫코드 로딩 기능으로 유명하다. 전화 교환 시스템처럼 99.999% 가동률이 필요한 시스템에서 사용된다.
7. 기술 부채 관리
기술 부채란?
빠른 개발을 위해 단기적으로 선택한 차선의 솔루션이 장기적으로 누적되어 개발 속도를 저하시키는 현상이다.
기술 부채의 유형
의도적 + 신중한: "지금은 단순 구현으로 가고, 다음 분기에 리팩토링하자"
의도적 + 무모한: "설계는 나중에 하고 일단 빨리 만들자"
비의도적 + 신중한: "구현 후 돌아보니 더 나은 설계가 있었다"
비의도적 + 무모한: "레이어드 아키텍처가 뭐지?"
기술 부채 측정
# 기술 부채 측정 지표 예시
class TechDebtMetrics:
def calculate_debt_ratio(self):
metrics = {
# 코드 품질
"test_coverage": self.get_test_coverage(), # 목표: 80%+
"code_duplication": self.get_duplication_rate(), # 목표: 5% 미만
"cyclomatic_complexity": self.get_avg_complexity(),# 목표: 10 미만
# 의존성 건강
"outdated_deps": self.count_outdated_dependencies(),
"security_vulns": self.count_known_vulnerabilities(),
# 빌드/배포 건강
"build_time_minutes": self.get_avg_build_time(),
"deploy_frequency_per_day": self.get_deploy_frequency(),
"change_failure_rate": self.get_change_failure_rate(), # 목표: 15% 미만
"mttr_hours": self.get_mean_time_to_recovery(), # 목표: 1시간 미만
}
return metrics
리팩토링 전략
보이스카우트 규칙: 코드를 발견한 것보다 더 깨끗하게 남겨라. 기능 개발 중 지나가는 코드를 조금씩 개선한다.
골든 패스 먼저: 가장 많이 사용되는 코드 경로(Happy Path)부터 리팩토링한다.
교살자 패턴(Strangler Fig): 레거시 시스템을 한 번에 교체하지 않고, 새로운 시스템으로 점진적으로 트래픽을 이동시킨다.
Phase 1: 레거시(100%) ─── 신규(0%)
Phase 2: 레거시(70%) ─── 신규(30%)
Phase 3: 레거시(30%) ─── 신규(70%)
Phase 4: 레거시(0%) ─── 신규(100%) ← 레거시 제거
8. 장애 대응
포스트모텀 (Postmortem)
장애가 발생한 후 작성하는 분석 문서다. **비난 없는 문화(Blameless Culture)**가 핵심이다.
# 포스트모텀 템플릿
## 장애 요약
- 발생 시각: 2026-04-10 14:23 KST
- 복구 시각: 2026-04-10 15:07 KST
- 영향 범위: 전체 사용자의 약 30% (결제 서비스)
- 심각도: P1 (Critical)
## 타임라인
- 14:23 - 결제 성공률 급감 알림 발생
- 14:25 - 온콜 엔지니어 대응 시작
- 14:32 - 근본 원인 파악 (DB 커넥션 풀 고갈)
- 14:45 - 긴급 패치 배포 시작
- 15:07 - 전체 서비스 정상 복구 확인
## 근본 원인
새로 배포된 쿼리에 인덱스가 누락되어 응답 시간이
기존 50ms에서 5초로 증가. 이로 인해 커넥션 풀이 고갈됨.
## 조치 사항
- [완료] 누락된 인덱스 추가
- [진행중] 슬로우 쿼리 자동 감지 시스템 구축
- [예정] 배포 전 쿼리 성능 테스트 자동화
SRE (Site Reliability Engineering)
구글이 만든 개념으로, 소프트웨어 엔지니어링 방법론을 운영에 적용하는 것이다.
핵심 개념:
- SLI (Service Level Indicator): 측정 가능한 지표 (응답 시간, 가용성 등)
- SLO (Service Level Objective): 목표 수치 (99.9% 가용성)
- SLA (Service Level Agreement): 고객과의 계약 (위반 시 크레딧 제공)
- 에러 버짓: SLO 달성률에 여유분이 있으면 새 기능 릴리스, 부족하면 안정성에 집중
SLO: 99.95% 가용성 (월간)
이번 달 현재: 99.97%
에러 버짓 남은 양: 0.02%
상태: 새로운 기능 배포 가능
만약 99.93%라면?
에러 버짓 소진: 초과 사용
상태: 안정성 개선에만 집중
온콜 (On-Call) 문화
온콜 로테이션 예시:
주간 1: 엔지니어 A (Primary), 엔지니어 B (Secondary)
주간 2: 엔지니어 B (Primary), 엔지니어 C (Secondary)
주간 3: 엔지니어 C (Primary), 엔지니어 D (Secondary)
알림 에스컬레이션:
0분: PagerDuty → Primary 엔지니어 (앱 알림 + 전화)
5분: 미응답 → Secondary 엔지니어
15분: 미응답 → 팀 리드
30분: 미해결 → VP of Engineering
9. 문서화와 지식 관리
ADR (Architecture Decision Record)
아키텍처 결정을 문서화하는 경량 프레임워크다.
# ADR 0015: 메시지 큐로 Apache Kafka 선택
## 상태
승인됨 (2026-03-15)
## 맥락
주문 처리 시스템에서 서비스 간 비동기 통신이 필요하다.
후보: Kafka, RabbitMQ, AWS SQS
## 결정
Apache Kafka를 선택한다.
## 이유
- 높은 처리량 (초당 수십만 건)
- 이벤트 소싱 패턴에 적합
- 메시지 재처리(replay) 가능
- 팀에 Kafka 운영 경험 보유
## 결과
- Kafka 클러스터 운영 비용 발생
- 팀원 대상 Kafka 교육 필요
- 모니터링 도구(Kafka UI) 도입 필요
RFC (Request for Comments)
큰 기술적 변경 전에 팀 전체의 피드백을 받는 프로세스다.
RFC 흐름:
1. 작성자가 RFC 문서 초안 작성 (1-2일)
2. 관련 팀에 리뷰 요청
3. 코멘트 및 토론 기간 (1-2주)
4. 최종 결정 (승인/반려/수정)
5. 구현 시작
주요 기업의 관행:
- 구글: Design Docs 문화가 매우 강하다. 중요한 변경은 반드시 문서부터 작성한다.
- 메타: Workplace에서 RFC를 공유하고 토론한다.
- 아마존: 6-pager 문서와 내러티브 기반의 회의 문화가 유명하다.
위키와 지식 베이스
추천 문서화 도구 조합:
코드 레벨: JSDoc / docstring + 자동 생성 API 문서
프로젝트 레벨: README.md + CONTRIBUTING.md + CHANGELOG.md
팀 레벨: Notion / Confluence 위키
조직 레벨: 검색 가능한 중앙 지식 베이스
"코드는 어떻게(How)를 설명하고, 문서는 왜(Why)를 설명한다."
10. 오픈소스의 힘
InnerSource (내부 오픈소스)
오픈소스 개발 방법론을 기업 내부에 적용하는 것이다.
일반적인 기업 개발:
팀 A → 라이브러리 X 요청 → 팀 B → 백로그 대기 (2-3개월)
InnerSource 방식:
팀 A → 라이브러리 X에 직접 PR 제출 → 팀 B 리뷰 → 머지 (1-2주)
InnerSource의 핵심 원칙:
- 모든 코드가 조직 내에서 공개되어 있다
- 누구나 다른 팀의 코드에 기여할 수 있다
- 각 프로젝트에는 관리자(Trusted Committer)가 있다
- 기여 가이드라인이 문서화되어 있다
커뮤니티 기반 개발
대규모 오픈소스 프로젝트가 관리되는 방식은 기업 내부에서도 참고할 점이 많다.
| 프로젝트 | 기여자 수 | 거버넌스 모델 |
|---|---|---|
| Linux Kernel | 약 15,000+ | BDFL (Linus) + Maintainer 계층 |
| Kubernetes | 약 3,000+ | SIG (Special Interest Group) 기반 |
| React | 약 1,500+ | Facebook 주도 + 커뮤니티 RFC |
| Rust | 약 5,000+ | RFC + 팀 기반 거버넌스 |
좋은 오픈소스 프로젝트의 공통점:
- 명확한 기여 가이드(CONTRIBUTING.md)
- 자동화된 CI/CD (PR 제출 시 자동 테스트)
- 코드 리뷰 문화
- 친절하고 포용적인 커뮤니티
- 정기적인 릴리스 주기
실전 체크리스트
대규모 소프트웨어를 운영하거나 도입할 때 참고할 체크리스트다.
아키텍처
[ ] 서비스 경계가 명확하게 정의되어 있는가?
[ ] API 버전 관리 전략이 있는가?
[ ] 서킷 브레이커 / 재시도 정책이 구현되어 있는가?
CI/CD
[ ] 코드 커밋부터 프로덕션 배포까지 자동화되어 있는가?
[ ] 롤백이 5분 이내에 가능한가?
[ ] 카나리/블루-그린 배포 전략이 있는가?
업데이트 시스템
[ ] 자동 업데이트가 구현되어 있는가?
[ ] 업데이트 파일의 무결성 검증(체크섬, 서명)을 하는가?
[ ] 업데이트 실패 시 롤백 메커니즘이 있는가?
모니터링
[ ] SLI/SLO가 정의되어 있는가?
[ ] 알림 에스컬레이션 정책이 있는가?
[ ] 분산 트레이싱이 구현되어 있는가?
기술 부채
[ ] 기술 부채를 주기적으로 측정하고 있는가?
[ ] 리팩토링에 할당된 엔지니어링 시간이 있는가?
[ ] 의존성 업데이트 정책이 있는가?
문서화
[ ] ADR을 작성하고 있는가?
[ ] API 문서가 자동 생성되는가?
[ ] 온보딩 문서가 최신 상태인가?
마무리
거대 소프트웨어의 개발과 유지보수는 단순히 "코딩을 잘하는 것"이 아니다. 아키텍처 설계, 빌드 시스템, 배포 전략, 업데이트 메커니즘, 장애 대응, 문서화, 팀 문화까지 모든 것이 하나의 시스템으로 유기적으로 작동해야 한다.
핵심 교훈을 정리하면 다음과 같다.
- 작게 만들고, 자주 배포하라 - 큰 릴리스보다 작은 변경의 연속이 안전하다
- 자동화할 수 있으면 자동화하라 - CI/CD, 테스트, 업데이트, 모니터링 모두
- 실패를 전제로 설계하라 - 서킷 브레이커, 롤백, 카나리 배포는 필수
- 업데이트는 안전하게 - 체크섬 검증, 서명 확인, 롤백 메커니즘을 갖추어야 한다
- 문서화는 투자다 - ADR, RFC, 포스트모텀은 미래의 나를 위한 것이다
- 기술 부채는 이자가 붙는다 - 주기적으로 갚지 않으면 개발 속도가 급감한다
좋은 소프트웨어는 한 번에 만들어지지 않는다. 끊임없이 개선하고, 안전하게 배포하고, 실패에서 배우는 과정의 반복이다.
참고 자료
- Google Engineering Practices: 구글의 코드 리뷰 가이드와 대규모 소프트웨어 개발 사례
- Software Engineering at Google (O'Reilly): 구글의 소프트웨어 엔지니어링 철학과 실천
- The Site Reliability Workbook (O'Reilly): SRE 실무 가이드
- Martin Fowler - Microservices: 마이크로서비스 아키텍처의 기본 개념
- Electron auto-updater 공식 문서: 데스크톱 앱 자동 업데이트 구현 가이드
- The Twelve-Factor App: 현대적 웹 앱 개발의 12가지 원칙
현재 단락 (1/565)
우리가 매일 사용하는 크롬 브라우저, 안드로이드 OS, 넷플릭스 앱은 수십억 줄의 코드로 이루어진 거대한 소프트웨어다.