포스트

데이터 중심 애플리케이션 설계12


데이터 시스템의 미래

개요

12장에서는 단순히 현재의 시스템을 설명하는 것을 넘어, 미래의 데이터 시스템이 어떻게 설계되고 구축되어야 하는지에 대한 저자의 견해를 제시합니다.


1. 데이터 통합 및 파생 데이터 (Data Integration and Derived Data)

데이터 흐름 시스템의 필요성

현대 애플리케이션에서는 다양한 도구를 결합하여 데이터를 통합해야 합니다. 배치 및 스트림 처리 시스템이 이러한 통합의 핵심 역할을 담당합니다.

파생 데이터셋의 예시:

1
2
3
4
5
6
7
원본 데이터: User Events Log
    ↓
파생 데이터:
├── Search Index (Elasticsearch)
├── Materialized Views (분석용 집계)
├── Recommendation System (ML 모델)
└── Real-time Metrics (모니터링 대시보드)

점진적인 진화 (Gradual Evolution)

기존 방식의 문제: 대규모 스키마 변경 시 서비스 중단 불가피

파생 뷰를 활용한 해결책:

1
2
3
4
Phase 1: Old Schema + Old Code (100% 트래픽)
Phase 2: Old Schema + Old Code (90%) + New Schema + New Code (10%)
Phase 3: Old Schema + Old Code (50%) + New Schema + New Code (50%)
Phase 4: New Schema + New Code (100%)

실무 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 기존 사용자 테이블
CREATE TABLE users_v1 (
    id BIGINT,
    name VARCHAR(255),
    email VARCHAR(255)
);

-- 점진적 마이그레이션: 파생 뷰 생성
CREATE VIEW users_v2 AS
SELECT
    id,
    SUBSTRING_INDEX(name, ' ', 1) as first_name,
    SUBSTRING_INDEX(name, ' ', -1) as last_name,
    email
FROM users_v1;

람다 아키텍처 (Lambda Architecture)

핵심 개념: 과거 데이터 재처리와 실시간 업데이트를 결합

1
2
3
4
5
6
7
8
9
10
Real-time Layer (실시간):
Events → Storm/Flink → Redis/HBase → Query Results

Batch Layer (배치):
Events → Hadoop/Spark → HDFS → Query Results
           ↓
     (주기적 재처리)

Serving Layer:
Real-time Results + Batch Results → 최종 결과

실무 적용 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 실시간: 최근 1시간 페이지뷰
@Service
public class RealtimeAnalytics {
    public long getRecentPageViews(String page) {
        return redisTemplate.opsForValue()
            .get("pageviews:recent:" + page);
    }
}

// 배치: 전체 기간 페이지뷰 (매시간 갱신)
@Service
public class BatchAnalytics {
    public long getTotalPageViews(String page) {
        return jdbcTemplate.queryForObject(
            "SELECT total_views FROM page_stats WHERE page = ?",
            Long.class, page);
    }
}

데이터베이스 비번들링 (Unbundling Databases)

핵심 아이디어: 애플리케이션 코드를 파생 함수로 사용하여 데이터 흐름을 명확화

스프레드시트 모델: 입력 변경 시 자동으로 파생 데이터 재계산

1
2
3
4
5
6
A1: 100 (주문 금액)
B1: 0.1 (할인율)
C1: =A1*(1-B1) (최종 금액: 90) ← 자동 계산

데이터 시스템에서:
Order Amount Changed → Trigger → Recalculate Final Amount

비동기 메시지 스트림의 장점

전통적 동기식 API:

1
2
3
Client → Service A → Service B → Service C
    ← 응답 ← 응답 ← 응답
(전체 지연시간 = A + B + C)

비동기 메시지 스트림:

1
2
3
Client → [Queue] → Service A → [Queue] → Service B → [Queue] → Service C
   ↓                 ↓                     ↓                     ↓
즉시 응답        백그라운드 처리      백그라운드 처리      백그라운드 처리

실무 구현 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 주문 처리: 동기식 vs 비동기식
@RestController
public class OrderController {

    // 동기식: 모든 처리 완료까지 대기
    @PostMapping("/order/sync")
    public ResponseEntity<String> createOrderSync(@RequestBody Order order) {
        orderService.save(order);           // DB 저장
        inventoryService.updateStock(order); // 재고 업데이트
        emailService.sendConfirmation(order); // 이메일 발송
        return ResponseEntity.ok("Order processed");
    }

    // 비동기식: 즉시 응답, 백그라운드 처리
    @PostMapping("/order/async")
    public ResponseEntity<String> createOrderAsync(@RequestBody Order order) {
        orderService.save(order);
        eventPublisher.publish(new OrderCreatedEvent(order));
        return ResponseEntity.ok("Order received"); // 즉시 응답
    }
}

2. 정확성 및 무결성 목표 (Aiming for Correctness)

정확히 한 번 처리 (Exactly-Once Semantics)

목표: 시스템 장애나 재시도에도 불구하고 모든 작업이 정확히 한 번만 실행되도록 보장

실무 시나리오:

1
2
3
결제 요청 처리 중 네트워크 오류 발생
→ 클라이언트가 재시도
→ 중복 결제 위험

멱등성 (Idempotence) 구현

고유한 작업 식별자 활용:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class PaymentService {

    @Transactional
    public PaymentResult processPayment(PaymentRequest request) {
        String idempotencyKey = request.getIdempotencyKey();

        // 이미 처리된 요청인지 확인
        PaymentResult existing = paymentRepository
            .findByIdempotencyKey(idempotencyKey);
        if (existing != null) {
            return existing; // 기존 결과 반환
        }

        // 새로운 결제 처리
        PaymentResult result = executePayment(request);
        result.setIdempotencyKey(idempotencyKey);
        paymentRepository.save(result);

        return result;
    }
}

API 레벨에서의 멱등성:

1
2
3
4
# 같은 키로 여러 번 호출해도 안전
curl -X POST /api/payments \
  -H "Idempotency-Key: txn_123456789" \
  -d '{"amount": 100, "currency": "USD"}'

무결성 vs 적시성 (Integrity vs Timeliness)

무결성 우선 원칙: 대부분의 비즈니스에서 데이터 정확성이 속도보다 중요

실무 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class BankTransferService {

    // 잘못된 방식: 속도 우선
    public void transferFundsUnsafe(Long fromAccount, Long toAccount, BigDecimal amount) {
        // 잔액 확인 없이 즉시 실행
        accountRepository.decreaseBalance(fromAccount, amount);
        accountRepository.increaseBalance(toAccount, amount);
    }

    // 올바른 방식: 무결성 우선
    @Transactional
    public TransferResult transferFundsSafe(Long fromAccount, Long toAccount, BigDecimal amount) {
        // 1. 잔액 충분성 검증
        Account from = accountRepository.findByIdForUpdate(fromAccount);
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException();
        }

        // 2. 원자적 트랜잭션으로 실행
        from.decreaseBalance(amount);
        Account to = accountRepository.findByIdForUpdate(toAccount);
        to.increaseBalance(amount);

        // 3. 결과 기록 (감사 목적)
        TransferResult result = new TransferResult(fromAccount, toAccount, amount);
        transferLogRepository.save(result);

        return result;
    }
}

조정 회피 시스템 (Coordination-Avoiding Systems)

목표: 분산 트랜잭션 없이도 강력한 무결성 보장

CRDT (Conflict-free Replicated Data Types) 활용:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 예시: 분산 카운터
public class GCounterCRDT {
    private Map<String, Long> counters = new HashMap<>();

    public void increment(String nodeId) {
        counters.merge(nodeId, 1L, Long::sum);
    }

    public long getValue() {
        return counters.values().stream()
            .mapToLong(Long::longValue)
            .sum();
    }

    // 다른 노드와 병합 (항상 동일한 결과)
    public void merge(GCounterCRDT other) {
        other.counters.forEach((nodeId, value) ->
            this.counters.merge(nodeId, value, Long::max));
    }
}

이벤트 소싱을 통한 조정 회피:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
public class BankAccount {
    private String accountId;
    private List<AccountEvent> events = new ArrayList<>();

    // 현재 잔액은 이벤트로부터 계산
    public BigDecimal getBalance() {
        return events.stream()
            .map(AccountEvent::getAmountChange)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    // 새 이벤트 추가 (조정 없이 가능)
    public void addEvent(AccountEvent event) {
        events.add(event);
    }
}

3. 윤리적 책임: “올바른 일 하기” (Doing the Right Thing)

엔지니어의 도덕적 상상력

핵심 메시지: 기술자들은 자신이 구축하는 시스템의 사회적 영향을 고려해야 할 책임이 있습니다.

실무에서의 고민 사례:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 추천 알고리즘 설계 시 고려사항
@Service
public class RecommendationService {

    public List<Content> getRecommendations(User user) {
        // 기술적으로는 가능하지만 윤리적 고려 필요:

        // 1. 사용자 취약성 악용하지 않기
        if (user.hasAddictionHistory()) {
            // 중독성 콘텐츠 필터링
        }

        // 2. 편향 증폭 방지
        // 단순 클릭률만 최적화하면 극단적 콘텐츠 선호

        // 3. 다양성 보장
        // 편향된 정보만 노출되지 않도록 균형 고려

        return generateBalancedRecommendations(user);
    }
}

예측 분석과 편향 문제

과거 차별의 답습 위험:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 문제가 있는 채용 알고리즘 예시
def evaluate_candidate(resume_data):
    # 과거 채용 데이터로 훈련된 모델
    # → 기존 편향을 그대로 학습

    score = ml_model.predict(resume_data)

    # 성별, 인종 등에 따른 암묵적 차별 가능
    return score

# 개선된 접근 방식
def evaluate_candidate_fair(resume_data):
    # 1. 편향 요소 제거
    cleaned_data = remove_bias_indicators(resume_data)

    # 2. 공정성 메트릭 적용
    score = ml_model.predict(cleaned_data)

    # 3. 결과 검증
    if not passes_fairness_check(score, resume_data):
        # 인간 검토 요청
        return request_human_review(resume_data)

    return score

감시와 프라이버시 문제

“데이터” → “감시” 관점 전환:

1
2
3
데이터 수집 → 감시 인프라
사용자 분석 → 사용자 추적
개인화 → 프로파일링

실무에서의 프라이버시 보호:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class UserDataService {

    // 데이터 최소화 원칙
    public UserProfile getProfileForRecommendation(Long userId) {
        return UserProfile.builder()
            .interests(getInterests(userId))
            .ageGroup(getAgeGroup(userId))
            // 개인 식별 정보는 제외
            .build();
    }

    // 목적 제한 원칙
    public void collectUserBehavior(UserAction action) {
        if (action.getType() == ActionType.PURCHASE) {
            // 구매 데이터는 추천에만 사용
            recommendationService.updatePreferences(action);
        } else {
            // 다른 용도로 사용 금지
        }
    }

    // 데이터 보존 기간 제한
    @Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 매일
    public void cleanupOldData() {
        userActivityRepository.deleteOlderThan(
            LocalDateTime.now().minusMonths(6)
        );
    }
}

정보화 시대의 “오염 문제”: 저자는 데이터 축적을 환경 오염에 비유합니다:

  • 개별 기업의 합리적 선택이 사회 전체에는 해로움
  • 규제와 업계 표준이 필요
  • 기술자 개인의 윤리적 선택이 중요

4. 서버 개발자를 위한 실무 가이드

데이터 통합 패턴 구현

Change Data Capture (CDC) 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Debezium 설정 예시
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnect
metadata:
  name: debezium-mysql-connector
spec:
  config:
    database.hostname: mysql-server
    database.port: 3306
    database.user: debezium
    database.password: dbz
    database.server.id: 184054
    database.server.name: inventory
    table.include.list: inventory.customers,inventory.products
    database.history.kafka.bootstrap.servers: kafka:9092
    database.history.kafka.topic: schema-changes.inventory

이벤트 소싱 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Entity
public class OrderAggregate {
    @Id
    private String orderId;

    @ElementCollection
    @OrderColumn
    private List<OrderEvent> events = new ArrayList<>();

    // 이벤트 추가
    public void apply(OrderEvent event) {
        events.add(event);
        // 상태 업데이트 로직
    }

    // 현재 상태 계산
    public OrderState getCurrentState() {
        return events.stream()
            .reduce(new OrderState(),
                   (state, event) -> event.apply(state),
                   (s1, s2) -> s2);
    }
}

무결성 보장 패턴

Saga 패턴 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class OrderSaga {

    @SagaOrchestrationStart
    public void processOrder(OrderCreatedEvent event) {
        // 1. 재고 예약
        sagaManager.choreography()
            .step("reserve-inventory")
            .compensatedBy("release-inventory")
            .invoke(inventoryService::reserve, event.getOrderId());

        // 2. 결제 처리
        sagaManager.choreography()
            .step("process-payment")
            .compensatedBy("refund-payment")
            .invoke(paymentService::charge, event.getPaymentInfo());

        // 3. 배송 준비
        sagaManager.choreography()
            .step("prepare-shipping")
            .invoke(shippingService::prepare, event.getShippingInfo());
    }
}

모니터링과 관찰성

데이터 품질 메트릭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
public class DataQualityMonitor {

    private final MeterRegistry meterRegistry;

    @EventListener
    public void onDataProcessed(DataProcessedEvent event) {
        // 데이터 신선도 측정
        Duration age = Duration.between(
            event.getCreatedAt(),
            Instant.now()
        );

        Timer.Sample.start(meterRegistry)
            .stop(Timer.builder("data.processing.latency")
                .tag("source", event.getSource())
                .register(meterRegistry));

        // 데이터 완성도 측정
        double completeness = calculateCompleteness(event.getData());
        Gauge.builder("data.completeness.ratio")
            .tag("dataset", event.getDataset())
            .register(meterRegistry, completeness);
    }
}

미래 데이터 시스템의 설계 원칙

1. 데이터 흐름 중심 아키텍처

  • 비동기 메시지 스트림 우선 고려
  • 파생 데이터를 활용한 점진적 진화
  • 이벤트 소싱으로 변경 이력 보존

2. 무결성 우선 설계

  • 멱등성 있는 API 설계
  • 조정 회피를 통한 확장성 확보
  • 정확히 한 번 처리 보장

3. 윤리적 기술 구축

  • 프라이버시 보호 기본 설계
  • 알고리즘 편향 지속적 모니터링
  • 사회적 영향 고려한 기능 개발

4. 운영 관점의 고려사항

  • 관찰성: 데이터 흐름과 품질 모니터링
  • 복구 가능성: 장애 시 데이터 일관성 보장
  • 점진적 배포: 위험을 최소화한 시스템 개선
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.