🏢 Stripe 기술 스택 분석
스트라이프가 전 세계 수백만 비즈니스의 결제를 처리하는 기술 스택 심층 분석 - Ruby, Go, Java로 구축한 금융 인프라의 미래
🏢 Stripe 기술 스택 분석
개발자를 위한 결제 인프라를 구축하며 금융의 미래를 만드는 Stripe의 기술 스택을 심층 분석합니다.
"Increase the GDP of the internet" - 인터넷 경제의 기반을 만드는 기술
📊 회사 개요
서비스 규모
- 연간 결제 처리량: $817B+ (2023)
- 비즈니스 고객: 수백만 개
- 서비스 국가: 45+ 개국
- API 요청: 연간 5,000억+ 건
- 가용성: 99.999% (Five nines)
엔지니어링 조직
- 엔지니어: 2,500명+ (전체 직원 8,000+)
- 문화: "Move fast with stable infrastructure"
- 원격 우선: 전 세계 분산 팀
- 기술 철학: API 우선, 개발자 경험 중심
기술적 도전과제
- 금융 규제 준수: 각국 규제 대응
- 보안: 민감한 금융 데이터 보호
- 신뢰성: 다운타임 제로 목표
- 확장성: 폭발적 성장 대응
🏗️ 아키텍처 Overview
시스템 다이어그램
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ API │────►│ Gateway │────►│ Payment │
│ Clients │ │ (Kong) │ │ Engine │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐
│ │Rate Limiter│ │Risk Engine │
│ │ (Redis) │ │ (ML) │
│ └───────────┘ └───────────┘
│ │ │
└────────────────────┤ │
┌─────▼─────┐ ┌─────▼─────┐
│ Ledger DB │ │ Analytics │
│(PostgreSQL)│ │ (Presto) │
└───────────┘ └───────────┘
핵심 설계 원칙
- API-First: 모든 기능은 API로
- Idempotency: 멱등성 보장
- Eventual Consistency: 최종 일관성
- Defense in Depth: 다층 보안
- Observability: 완전한 가시성
기술 진화
- 2010-2012: Ruby 모놀리스
- 2013-2015: 서비스 분리 시작
- 2016-2018: Go 도입, 마이크로서비스
- 2019-2021: 글로벌 확장, ML 플랫폼
- 2022-현재: 임베디드 금융, 암호화폐
🧩 기술 스택 상세
Backend 기술
결제 처리 엔진
# Stripe의 Ruby 기반 결제 처리
module Stripe
class PaymentProcessor
include Retriable
include Idempotent
def process_payment(payment_intent)
# 멱등성 키 확인
idempotency_key = payment_intent.idempotency_key
if result = IdempotencyStore.get(idempotency_key)
return result
end
# 트랜잭션 시작
transaction do
# 1. 유효성 검증
validate_payment_intent(payment_intent)
# 2. 위험 평가
risk_score = RiskEngine.evaluate(payment_intent)
if risk_score.high_risk?
return handle_high_risk_payment(payment_intent, risk_score)
end
# 3. 결제 수단 확인
payment_method = PaymentMethod.find(payment_intent.payment_method_id)
verify_payment_method(payment_method)
# 4. 잔액 확인 및 보류
amount = payment_intent.amount
currency = payment_intent.currency
hold = create_balance_hold(
payment_method: payment_method,
amount: amount,
currency: currency
)
# 5. 네트워크 요청 (카드사, 은행 등)
network_response = process_with_network(
payment_method: payment_method,
amount: amount,
metadata: payment_intent.metadata
)
# 6. 결과 처리
case network_response.status
when :approved
complete_payment(payment_intent, hold, network_response)
when :declined
release_hold(hold)
handle_decline(payment_intent, network_response)
when :requires_action
handle_3ds_authentication(payment_intent, network_response)
else
raise UnexpectedNetworkResponse, network_response
end
end
rescue => e
# 에러 처리 및 복구
handle_payment_error(payment_intent, e)
ensure
# 멱등성 결과 저장
IdempotencyStore.set(idempotency_key, result, expires_in: 24.hours)
end
private
def complete_payment(payment_intent, hold, network_response)
# 원장에 기록
ledger_entry = Ledger.record(
type: :payment,
debit_account: payment_intent.customer_account,
credit_account: payment_intent.merchant_account,
amount: payment_intent.amount,
currency: payment_intent.currency,
reference: network_response.authorization_code
)
# 보류 해제 및 실제 이체
transfer = Transfer.create!(
from_hold: hold,
to_account: payment_intent.merchant_account,
ledger_entry: ledger_entry
)
# 이벤트 발행
Events.publish(
'payment.succeeded',
payment_intent: payment_intent,
transfer: transfer,
timestamp: Time.now
)
# Webhook 트리거
WebhookDispatcher.dispatch_async(
event_type: 'payment_intent.succeeded',
account: payment_intent.merchant_account,
data: payment_intent.to_webhook_payload
)
payment_intent.update!(status: :succeeded)
end
end
end
Go 기반 고성능 서비스
// Stripe의 Go 기반 리스크 엔진
package risk
import (
"context"
"time"
"github.com/stripe/stripe-go/v72"
"github.com/stripe/veneur/trace"
)
type RiskEngine struct {
mlModels map[string]MLModel
ruleEngine *RuleEngine
dataStore DataStore
cache Cache
}
type RiskAssessment struct {
Score float64
Reasons []string
Action RiskAction
RequiredActions []string
}
func (re *RiskEngine) Evaluate(ctx context.Context, payment *Payment) (*RiskAssessment, error) {
span := trace.StartSpan("risk.evaluate")
defer span.End()
// 동시에 여러 신호 수집
signals := make(chan Signal, 10)
errors := make(chan error, 10)
go re.collectDeviceSignals(ctx, payment, signals, errors)
go re.collectBehaviorSignals(ctx, payment, signals, errors)
go re.collectNetworkSignals(ctx, payment, signals, errors)
go re.collectHistoricalSignals(ctx, payment, signals, errors)
// 타임아웃 설정
timeout := time.After(100 * time.Millisecond)
collectedSignals := []Signal{}
for i := 0; i < 4; i++ {
select {
case signal := <-signals:
collectedSignals = append(collectedSignals, signal)
case err := <-errors:
// 에러는 로그하지만 계속 진행
span.LogFields(trace.Error(err))
case <-timeout:
// 타임아웃 시 수집된 것만으로 진행
break
}
}
// ML 모델 예측
mlScore := re.runMLModels(ctx, payment, collectedSignals)
// 규칙 엔진 실행
ruleResults := re.ruleEngine.Evaluate(payment, collectedSignals)
// 최종 점수 계산
assessment := re.calculateFinalAssessment(mlScore, ruleResults)
// 결과 저장 (비동기)
go re.storeAssessment(ctx, payment.ID, assessment)
return assessment, nil
}
func (re *RiskEngine) runMLModels(ctx context.Context, payment *Payment, signals []Signal) float64 {
features := re.extractFeatures(payment, signals)
// 앙상블 모델 실행
predictions := make([]float64, 0, len(re.mlModels))
for name, model := range re.mlModels {
pred, err := model.Predict(features)
if err != nil {
continue
}
predictions = append(predictions, pred)
}
// 가중 평균
return re.weightedAverage(predictions)
}
// 실시간 사기 탐지 규칙
func (re *RiskEngine) detectVelocityAbuse(customerID string) bool {
// 슬라이딩 윈도우로 속도 제한 체크
key := fmt.Sprintf("velocity:%s", customerID)
windows := []struct {
duration time.Duration
limit int
}{
{time.Minute, 5}, // 분당 5건
{time.Hour, 20}, // 시간당 20건
{24 * time.Hour, 100}, // 일당 100건
}
for _, window := range windows {
count, err := re.cache.IncrementWindow(key, window.duration)
if err != nil {
return true // 에러 시 안전하게 차단
}
if count > window.limit {
return true
}
}
return false
}
원장 시스템 (Double-Entry Bookkeeping)
// Stripe의 Java 기반 원장 시스템
@Service
@Transactional
public class LedgerService {
private final LedgerRepository ledgerRepository;
private final AccountRepository accountRepository;
private final AuditLogger auditLogger;
public LedgerEntry recordTransaction(TransactionRequest request) {
// 원자적 트랜잭션 보장
return transactionTemplate.execute(status -> {
// 1. 계정 잠금 (데드락 방지를 위해 ID 순서대로)
List<Account> accounts = lockAccountsInOrder(
request.getDebitAccountId(),
request.getCreditAccountId()
);
Account debitAccount = accounts.get(0);
Account creditAccount = accounts.get(1);
// 2. 잔액 검증
if (!debitAccount.hasSufficientBalance(request.getAmount())) {
throw new InsufficientFundsException(
debitAccount.getId(),
request.getAmount()
);
}
// 3. 원장 항목 생성 (불변)
LedgerEntry entry = LedgerEntry.builder()
.id(UUID.randomUUID())
.transactionId(request.getTransactionId())
.debitAccountId(debitAccount.getId())
.creditAccountId(creditAccount.getId())
.amount(request.getAmount())
.currency(request.getCurrency())
.description(request.getDescription())
.metadata(request.getMetadata())
.timestamp(Instant.now())
.build();
// 4. 잔액 업데이트
debitAccount.debit(request.getAmount());
creditAccount.credit(request.getAmount());
// 5. 영속화
ledgerRepository.save(entry);
accountRepository.saveAll(Arrays.asList(debitAccount, creditAccount));
// 6. 감사 로그
auditLogger.log(AuditEvent.builder()
.type(AuditEventType.LEDGER_ENTRY_CREATED)
.entityId(entry.getId())
.entityType("LedgerEntry")
.userId(request.getInitiatorId())
.changes(entry.toMap())
.timestamp(Instant.now())
.build()
);
// 7. 이벤트 발행
publishLedgerEvent(entry);
return entry;
});
}
// 일일 정산 및 검증
@Scheduled(cron = "0 0 2 * * *") // 매일 오전 2시
public void runDailyReconciliation() {
LocalDate yesterday = LocalDate.now().minusDays(1);
// 모든 계정의 데빗/크레딧 합계 계산
BigDecimal totalDebits = ledgerRepository
.sumDebitsForDate(yesterday);
BigDecimal totalCredits = ledgerRepository
.sumCreditsForDate(yesterday);
// 복식부기 원칙: 데빗 = 크레딧
if (!totalDebits.equals(totalCredits)) {
alertOps(
"Ledger imbalance detected",
Map.of(
"date", yesterday,
"debits", totalDebits,
"credits", totalCredits,
"difference", totalDebits.subtract(totalCredits)
)
);
}
// 각 계정별 잔액 검증
reconcileAccountBalances(yesterday);
}
}
Frontend 기술
React 기반 대시보드
// Stripe Dashboard 컴포넌트
import React, { useEffect, useState } from "react";
import { useStripe } from "@stripe/react-stripe-js";
import { LineChart, BarChart } from "recharts";
interface DashboardProps {
accountId: string;
}
export const StripeDashboard: React.FC<DashboardProps> = ({ accountId }) => {
const stripe = useStripe();
const [metrics, setMetrics] = useState<DashboardMetrics>();
const [timeRange, setTimeRange] = useState<TimeRange>("7d");
useEffect(() => {
loadDashboardData();
}, [accountId, timeRange]);
const loadDashboardData = async () => {
// 병렬로 여러 메트릭 로드
const [revenue, transactions, customers, disputes, payouts] =
await Promise.all([
fetchRevenueMetrics(timeRange),
fetchTransactionMetrics(timeRange),
fetchCustomerMetrics(timeRange),
fetchDisputeMetrics(timeRange),
fetchPayoutSchedule(),
]);
setMetrics({
revenue,
transactions,
customers,
disputes,
payouts,
});
};
return (
<div className="stripe-dashboard">
<Header>
<h1>Dashboard</h1>
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
</Header>
<MetricsGrid>
<MetricCard
title="Gross Volume"
value={formatCurrency(metrics?.revenue.total)}
change={metrics?.revenue.changePercent}
sparkline={metrics?.revenue.daily}
/>
<MetricCard
title="Successful Payments"
value={metrics?.transactions.successful.toLocaleString()}
subtitle={`${metrics?.transactions.successRate}% success rate`}
/>
<MetricCard
title="New Customers"
value={metrics?.customers.new.toLocaleString()}
change={metrics?.customers.changePercent}
/>
<MetricCard
title="Disputes"
value={metrics?.disputes.active}
subtitle={`${formatCurrency(metrics?.disputes.amount)} at risk`}
variant={metrics?.disputes.active > 0 ? "warning" : "default"}
/>
</MetricsGrid>
<ChartsSection>
<RevenueChart data={metrics?.revenue.hourly} />
<PaymentMethodBreakdown data={metrics?.transactions.byMethod} />
<GeographicDistribution data={metrics?.revenue.byCountry} />
</ChartsSection>
<PayoutSchedule payouts={metrics?.payouts} />
<RecentActivity />
</div>
);
};
// 실시간 활동 피드
const RecentActivity: React.FC = () => {
const [activities, setActivities] = useState<Activity[]>([]);
useEffect(() => {
// WebSocket 연결로 실시간 업데이트
const ws = new WebSocket("wss://dashboard.stripe.com/activity");
ws.onmessage = (event) => {
const activity = JSON.parse(event.data);
setActivities((prev) => [activity, ...prev].slice(0, 50));
};
return () => ws.close();
}, []);
return (
<ActivityFeed>
{activities.map((activity) => (
<ActivityItem key={activity.id}>
<ActivityIcon type={activity.type} />
<ActivityContent>
<ActivityDescription>{activity.description}</ActivityDescription>
<ActivityTime>
{formatRelativeTime(activity.timestamp)}
</ActivityTime>
</ActivityContent>
<ActivityAmount>
{activity.amount && formatCurrency(activity.amount)}
</ActivityAmount>
</ActivityItem>
))}
</ActivityFeed>
);
};
// Stripe Elements 통합
export const CheckoutForm: React.FC = () => {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState<string>();
const [processing, setProcessing] = useState(false);
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
if (!stripe || !elements) return;
setProcessing(true);
// 결제 수단 생성
const { error: methodError, paymentMethod } =
await stripe.createPaymentMethod({
type: "card",
card: elements.getElement(CardElement)!,
billing_details: {
name: formData.name,
email: formData.email,
address: formData.address,
},
});
if (methodError) {
setError(methodError.message);
setProcessing(false);
return;
}
// 서버로 결제 요청
const response = await fetch("/api/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
payment_method_id: paymentMethod.id,
amount: calculateTotal(),
currency: "usd",
}),
});
const result = await response.json();
if (result.requires_action) {
// 3D Secure 인증 필요
const { error: confirmError } = await stripe.confirmCardPayment(
result.payment_intent_client_secret
);
if (confirmError) {
setError(confirmError.message);
}
}
setProcessing(false);
};
return (
<form onSubmit={handleSubmit}>
<CardElement
options={{
style: stripeElementStyles,
hidePostalCode: true,
}}
/>
{error && <ErrorMessage>{error}</ErrorMessage>}
<Button type="submit" disabled={!stripe || processing}>
{processing ? <Spinner /> : `Pay ${formatCurrency(total)}`}
</Button>
</form>
);
};
인프라 및 신뢰성
분산 시스템 패턴
# Stripe의 분산 시스템 관리
import asyncio
from typing import List, Optional
import consul
import aioredis
class DistributedLock:
"""분산 잠금 구현"""
def __init__(self, redis_client: aioredis.Redis):
self.redis = redis_client
async def acquire(
self,
key: str,
ttl: int = 30,
retry_times: int = 10,
retry_delay: float = 0.1
) -> Optional[str]:
"""
Redlock 알고리즘 구현
"""
token = str(uuid.uuid4())
for _ in range(retry_times):
# SET NX EX 원자적 실행
acquired = await self.redis.set(
key,
token,
expire=ttl,
exist=False
)
if acquired:
return token
await asyncio.sleep(retry_delay)
return None
async def release(self, key: str, token: str) -> bool:
"""
Lua 스크립트로 안전한 해제
"""
lua_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
result = await self.redis.eval(lua_script, [key], [token])
return bool(result)
class CircuitBreaker:
"""서킷 브레이커 패턴"""
def __init__(
self,
failure_threshold: int = 5,
recovery_timeout: int = 60,
expected_exception: type = Exception
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.expected_exception = expected_exception
self.failure_count = 0
self.last_failure_time = None
self.state = 'closed' # closed, open, half-open
async def call(self, func, *args, **kwargs):
if self.state == 'open':
if self._should_attempt_reset():
self.state = 'half-open'
else:
raise CircuitOpenError("Circuit breaker is open")
try:
result = await func(*args, **kwargs)
self._on_success()
return result
except self.expected_exception as e:
self._on_failure()
raise
def _on_success(self):
self.failure_count = 0
self.state = 'closed'
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'open'
class RateLimiter:
"""토큰 버킷 기반 rate limiter"""
def __init__(self, rate: int, burst: int):
self.rate = rate # tokens per second
self.burst = burst # max tokens
self.tokens = burst
self.last_update = time.time()
self.lock = asyncio.Lock()
async def acquire(self, tokens: int = 1) -> bool:
async with self.lock:
now = time.time()
elapsed = now - self.last_update
self.last_update = now
# 토큰 리필
self.tokens = min(
self.burst,
self.tokens + elapsed * self.rate
)
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
보안 및 컴플라이언스
PCI 컴플라이언스
// Rust로 구현된 카드 번호 토큰화
use aes_gcm::{Aes256Gcm, Key, Nonce};
use ring::rand::{SecureRandom, SystemRandom};
pub struct CardTokenizer {
encryption_key: Key<Aes256Gcm>,
token_vault: TokenVault,
hsm_client: HsmClient,
}
impl CardTokenizer {
pub fn tokenize_card(&self, card_number: &str) -> Result<String, TokenizationError> {
// 1. 카드 번호 검증
if !self.validate_card_number(card_number) {
return Err(TokenizationError::InvalidCardNumber);
}
// 2. 형식 정규화
let normalized = card_number.chars()
.filter(|c| c.is_digit(10))
.collect::<String>();
// 3. 토큰 생성
let token = self.generate_token(&normalized);
// 4. HSM에서 암호화
let encrypted = self.hsm_client.encrypt(
normalized.as_bytes(),
&self.encryption_key
)?;
// 5. 토큰 볼트에 저장
self.token_vault.store(
&token,
&encrypted,
TokenMetadata {
created_at: Utc::now(),
last_four: &normalized[normalized.len()-4..],
card_brand: detect_card_brand(&normalized),
expiry_date: None, // 저장하지 않음
}
)?;
// 6. 감사 로그
audit_log!("card_tokenized", {
"token": &token,
"brand": detect_card_brand(&normalized),
"timestamp": Utc::now(),
});
Ok(token)
}
fn validate_card_number(&self, number: &str) -> bool {
// Luhn 알고리즘
let digits: Vec<u8> = number.chars()
.filter_map(|c| c.to_digit(10).map(|d| d as u8))
.collect();
if digits.len() < 13 || digits.len() > 19 {
return false;
}
let checksum: u8 = digits.iter()
.rev()
.enumerate()
.map(|(i, &d)| {
if i % 2 == 1 {
let doubled = d * 2;
if doubled > 9 { doubled - 9 } else { doubled }
} else {
d
}
})
.sum();
checksum % 10 == 0
}
fn generate_token(&self, seed: &str) -> String {
// 형식 보존 암호화 (Format Preserving Encryption)
let mut token = String::with_capacity(16);
token.push_str("tok_");
// 안전한 난수 생성
let rng = SystemRandom::new();
let mut bytes = [0u8; 12];
rng.fill(&mut bytes).unwrap();
// Base62 인코딩
token.push_str(&base62::encode(&bytes));
token
}
}
기술 스택 요약:
- Backend: Ruby, Go, Java, Rust
- Frontend: React, TypeScript
- 데이터베이스: PostgreSQL, MongoDB, Redis
- 메시지 큐: Kafka, NSQ
- 인프라: AWS, 자체 데이터센터
- 모니터링: Veneur, Datadog
- 보안: HSM, Vault
💡 핵심 기술 인사이트
1. API 설계의 교과서
- RESTful하면서도 실용적
- 뛰어난 개발자 경험
- 포괄적인 문서화
2. 금융 시스템의 신뢰성
- 복식부기 원장
- 완벽한 감사 추적
- 멱등성 보장
3. 글로벌 규모의 보안
- PCI DSS Level 1
- 엔드투엔드 암호화
- 실시간 사기 탐지
📈 성과 & 지표
기술적 성과
- 가용성: 99.999% (연간 5분 다운타임)
- API 응답시간: P50 < 100ms
- 처리 용량: 초당 65,000+ 요청
- 글로벌 지연시간: < 150ms
- 보안 사고: 0 (설립 이후)
비즈니스 영향
- 결제 처리량: 연간 $817B+
- 개발자 만족도: 96%
- 통합 시간: 평균 10줄 코드
- 수익 증가: 고객사 평균 6.7%
🎓 Stripe에서 배울 점
✅ 적용 가능한 패턴
- API 우선 설계: 모든 기능을 API로
- 멱등성: 분산 시스템 필수
- 점진적 마이그레이션: 모놀리스→마이크로서비스
- 개발자 경험: 문서와 도구의 중요성
- 감사 추적: 모든 변경사항 기록
❌ 주의사항
- 규제 복잡성: 각국 금융 법규
- 보안 요구사항: 극도로 높은 기준
- 레거시 시스템: 금융권 통합 어려움
- 다운타임 불가: 24/7 운영 부담
📚 추천 리소스
🔮 미래 전망
현재 집중 분야
- Stripe Treasury: 은행 서비스 API
- Stripe Capital: 대출 서비스
- Stripe Climate: 탄소 제거 투자
- Crypto: 암호화폐 결제
- Embedded Finance: 금융 인프라화
기술 투자 영역
- 기계학습: 더 정교한 사기 탐지
- 블록체인: 국경 간 결제
- 실시간 결제: 즉시 정산
- 개발자 도구: 더 나은 DX
- 글로벌 확장: 더 많은 국가
"우리는 인터넷의 결제 인프라를 구축하여, 모든 비즈니스가 글로벌하게 성장할 수 있도록 돕고 있습니다."
- Stripe Engineering
마지막 업데이트: 2025-01-28