1장. 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션

p.6

신뢰성(Reliability) 하드웨어나 소프트웨여 결함, 심지어 인적 오류(human error) 같은 역경에 직면하더라도 시스템은 지속적으로 올바르게 동작(원하는 성능 수준에서 정확한 기능을 수행)해야 한다.

확장성(Scalability) 시스템의 데이터 양, 트래픽 양, 복잡도가 증가하면서 이를 처리할 수 있는 적절한 방법이 있어야 한다.

유지보수성(Maintainability) 시간이 지남에 따라 여러 다양한 사람들이 시스템 상에서 작업(현재 작업을 유지보수하고 새로운 사용 사례를 시스템에 적용하는 엔지니어링과 운영)할 것이기 때문에 모든 사용자가 시스템 상에서 생산적으로 작업할 수 있게 해야 한다.

p.6-7

소프트웨어의 일반적인 기대치

  • 애플리케이션 사용자가 거대한 기능을 수행한다.
  • 시스템은 사용자가 범한 실수나 예상치 못한 소프트웨어 사용법을 허용할 수 있다.
  • 시스템 성능은 예상된 부하와 데이터 양에서 필수적인 사용 사례를 충분히 만족한다.
  • 시스템은 허가되지 않은 접근과 오남용을 방지한다.

잘못될 수 있는 일을 결함(fault)이라 부른다. 그리고 결함을 예측하고 대처할 수 있는 시스템을 내결함성(fault-tolerant) 또는 탄력성(resilient)을 지녔다고 말한다.

결함은 장애(failure)와 동일하지 않다. 일반적으로 결함은 사양에서 벗어난 시스템의 한 구성 요소로 정의되지만, 장애는 사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우다. … 대개 결함으로 인해 장애가 발생하지 않게끔 내결함성 구조를 설계하는 것이 가장 좋다.

p.9-10

대규모 인터넷 서비스에 대한 한 연구에 따르면 운영자의 설정 오류가 중단의 주요원인인 반면 하드웨어(서버나 네트워크) 결함은 중단 원인의 10~25% 정도에 그친다.

  • 오류의 가능성을 최소화하는 방향으로 시스템을 설계하라
  • 사람의 실수로 장애가 발생할 수 있는 부분을 분리하라
  • 모든 수준에서 철저하게 테스트하라
  • 인적 오류를 빠르고 쉽게 복구할 수 있게 하라
  • 모니터링 대책을 마연하라
  • 조작 교육과 실슴을 시행하라

p.11 부하는 부하 매개변수(load paramter)라 부르는 몇 개의 숫자로 나타낼 수 있다. … 부하 매개변수로 웹 서버의 초당 요청 수, 데이터베이스의 읽기 대 쓰기 비율, 대화방의 동시 활성 사용자(active user), 캐시 적중률 등이 될 수 있다.

p.19 유지보수성

운용성(operability) 운영팀이 시스템을 원활하게 운영할 수 있게 쉽게 만들어라.

단순성(Simplicity) 시스템에서 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들어라(사용자 인터페이스의 단순성과는 다르다는 점에 유의하라).

발전성(Evolvability) 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 하라. 그래야 요구사항 변경 같은 예기치 않은 사용 사례를 적용하기 쉽다. 이 속성은 유연성(extensibility), 수정 가능성(modifiability), 적응성(plasticity)으로 알려져 있다.

p.20 좋은 운영성

  • 좋은 모니터링으로 런타임(runtime) 동작과 시스템의 내부에 대한 가시성 제공
  • 표준 도구를 이용해 자동화와 통합을 위한 우수한 지원을 제공
  • 개별 장비 의존성을 회피. 유지보수를 위해 장비를 내리더라도 시스템 전체에 영향을 주지 않고 계속해서 운영 가능해야 함
  • 좋은 문서와 이해하기 쉬운 운영 모델(예를 들어 “X를 하면 Y가 발생한다”) 제공
  • 만족할 만한 기본 동작을 제공하고, 필요할 때 기본값을 다시 정의할 수 있는 자유를 관리자에게 부여
  • 적절하게 자기 회복(self-healing)이 가능할 뿐 아니라 필요에 따라 관리자가 시스템 상태를 수동으로 제어할 수 있게 함
  • 예측 가능하게 동작하고 예기치 않은 상황을 최소화함

2장. 데이터 모델과 질의 언어

p.32 지역성(locality). JSON 표현에서는 모든 관련 정보가 한 곳에 있어 질의 하나로 충분하다.

p.40 쓰기 스키마(schema-on-write)(관계형 데이터베이스의 전통적인 접근 방식으로 스키마는 명시적이고 데이터 베이스는 쓰여진 모든 데이터가 스키마를 따르고 있음을 보장한다)와 반대되는 읽기 스키마(schema-on-read)(데이터 구조는 암묵적이고 데이터를 읽을 때만 해석된다)

p.43 선언형 질의 SQL이나 관계 대수 같은 선언형 질의 언어에서는 목표를 달성하기 위한 방법이 아니라, 알고자 하는 데이터의 패턴, 즉 결과가 충족해야 하는 조건과 데이터를 어떻게 변환(예를 들어 정렬, 그룹화, 집계)할지를 지정하기만 하면 된다.


3장: 저장소와 검색

  • p.72 특정 작업부(workload) 유형에서 좋은 성능을 내게끔 저장소 엔진을 조정하려면 저장소 엔진이 내부에서 수행되는 작업에 대해 대략적인 개념을 이해할 필요가 있다.
  • p.74 색인을 잘 선택했다면 읽기 질의 속도가 향상된다. 하지만 모든 색인은 쓰기 속도를 떨어뜨린다.
  • p.74 키를 데이터 파일의 바이트 오프셋에 매핑해 인메모리 해시 맵을 유지하는 전략이다.
  • p.74 값을 조회하려면 해시 맵을 사용해 데이터 파일에서 오프셋을 찾아 해당 위치를 구하고 값을 읽는다.
  • p.75 컴팩션은 로그에서 중복된 키를 버리고 각 키의 최신 갱신 값만 유지하는 것을 의미한다.
  • p.77 불행하게도 디스크 상의 해시 맵에 좋은 성능을 기대하기란 어렵다. 이는 무작위 접근 I/O가 많이 필요하고 디스크가 가득 찼을 때 확장하는 비용이 비싸며 해시 충돌 해소를 위해 성가신 로직이 필요하다.
  • p.78 해시 테이블은 범위 질의(range query)에 효율적이지 않다.
  • p.79 그래도 handbag과 handsome 키의 오프셋을 알고 있고 정렬돼 있으므로 handiwork는 두 키 사이에 있다는 사실을 알 수 있다.
  • p.80 저장소 엔진
    • 쓰기가 들어오면 인메모리 균형 트리(balanced tree) 데이터 구조(예를 들어 레드 블랙 트리)에 추가한다. 이 인메모리 트리는 멤테이블(memtable)이라고도 한다.
    • 멤테이블이 보통 수 메가바이트 정도의 임곗값보다 커지면 SS테이블 파일로 디스크에 기록한다. 트리가 이미 키로 정렬된 키-값 쌍을 유지하고 있기 때문에 효율적으로 수행할 수 있다. 새로운 SS테이블 파일은 데이터베이스의 가장 최신 세그먼트가 된다. SS테이블을 디스크에 기록하는 동안 쓰기는 새로운 멤테이블 인스턴스에 기록한다.
    • 읽기 요청을 제공하려면 먼저 멤테이블에서 키를 찾아야 한다. 그다음 디스크 상의 가장 최신 세그먼트에서 찾는다. 그다음으로 두 번째 오래된 세그먼트, 세 번째 오래된 세그먼트 등에서 찾는다.
    • 가끔 세그먼트 파일을 합치고 덮어 쓰여지거나 삭제된 값을 버리는 병합과 컴팩션 과정을 수행한다. 이 과정은 백그라운드에서 수행된다.
  • p.80 정렬된 파일 병합과 컴팩션 원리를 기반으로 하는 저장소 엔진을 LSM 저장소 엔진이라 부른다.
  • p.81 키를 단어(용어)로, 값은 단어를 포함한 모든 문서의 ID 목록(포스팅 목록(postings list))으로 하는 키-값 구조로 구현한다.
  • p.81 블룸 필터는 키가 데이터베이스에 존재하지 않음을 알려주므로 존재하지 않는 키를 위한 불필요한 디스크 읽기를 많이 절약할 수 있다.
  • p.81 백그라운드에서 연쇄적으로 SS테이블을 지속적으로 병합하는 것이다. 이 개념은 데이터셋이 가능한 메모리보다 훨씬 더 크더라도 여전히 효과적이다. 데이터가 정렬된 순서로 저장돼 있다면 범위 질의를 효율적으로 실행할 수 있다.
  • p.82 B 트리는 전통적으로 4KB 크기(때로는 더 큰)의 고정 크기 블록이나 페이지로 나누고 한 번에 하나의 페이지에 읽기 또는 쓰기를 한다. 디스크가 고정 크기 블록으로 배열되기 때문에 이런 설계는 근본적으로 하드웨어와 조금 더 밀접한 관련이 있다.
  • p.83 새로운 키를 수용한 페이지에 충분한 여유 공간이 없다면 페이지 하나를 반쯤 채워진 페이지 둘로 나누고 상위 페이지가 새로운 키 범위의 하위 부분들을 알 수 있게 갱신한다.
  • p.99 칼럼 지향 저장소. 모든 값을 하나의 로우에 함께 저장하지 않는 대신 각 칼럼별로 모든 값을 함께 저장한다. 각 칼럼을 개별 파일에 저장하면 질의에 사용되는 칼럼만 읽고 구분 분석하면 된다. 이 방식을 사용하면 작업량이 많이 줄어든다.

05장: 복제

  • p.156 동기식 복제의 장점은 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 가지는 것을 보장한다. 갑자기 리더가 작동하지 않아도 데이터는 팔로워에서 계속 사용할 수 있음을 확신할 수 있다. 단점은 (팔로워가 죽거나 네트워크 문제나 다른 어떤 이유로 인해) 동기 팔로워가 응답하지 않는다면 쓰기가 처리될 수 없다는 것이다. 리더는 모든 쓰기를 차단(block)하고 동기 복제 서버가 다시 사용할 수 있을때까지 기다려야 한다.
  • p.159 팔로워 중 하나를 새로운 리더로 승격해야 하고 클라이언트는 새로운 리더로 쓰기를 전송하기 위해 재설정이 필요하며 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작해야 한다. 이 과정을 장애 복구(failover)라 한다.
  • p.167 Monotonic Read: 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 것
  • p.168 Consistent Prefix Read: 일련의 쓰기가 특정 순서로 발생한다면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게 됨을 보장한다.
  • p.174 충돌을 처리하는 제일 간단한 전략은 충돌을 피하는 것이다. 특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 애플리케이션이 보장한다면 충돌은 발생하지 않는다.
  • p.175 Automatic Conflict Resolution
  • p.179 Dynamo Style: 리더의 개념을 버리고 모든 복제 서버가 클라이언트로부터 쓰기를 직접 받을 수 있게 허용하는 접근 방식
  • p.180 Outdated 해결: 클라이언트가 데이터베이스에서 읽을 때 하나의 복제 서버로 요청을 보내지 않고 읽기 요청을 병렬로 여러 노드에 전송. 클라이언트는 여러 노드에서 다른 응답을 받음. 즉, 한 노드에서는 최신 값을 받고 다른 노드에서는 오래된 값을 받음. 이 때 버전 숫자를 사용해 어떤 값이 최신 내용인지 결정.
  • p.180 Anti-entropy
  • p.188. Concurrent: 한 작업이 다른 작업 이전에 발생했는지가 동시성의 의미를 정의하는 핵심. 사실 작업이 다른 작업보다 먼저 발생하지 않으면 (즉 어느 작업도 다른 작업에 대해 알지 못한다면) 단순히 동시 작업이라 말한다.
    • 동시성을 정의하기 위해 정확한 시각은 중요하지 않다. 두 작업이 발생한 물리적인 시각보다 각 작업이 서로 알지 못하면 단순히 두 작업은 동시에 수행됐다 말한다.

06장: 파티셔닝

  • p199 데이터셋이 매우 크거나 질의 처리량이 매우 높다면 복제만으로는 부족하고 데이터를 파티션으로 쪼갤 필요가 있다. 이 작업을 샤딩이라고도 한다.
  • p200 파티셔닝하는 주된 이유는 확장성. 단일 파티션에 실행되는 질의를 생각해 보면 각 노드에서 자신의 파티션에 해당하는 질의를 독립적으로 실행할 수 있으므로 노드를 추가함으로써 질의 처리량을 늘릴 수 있다. 크고 복잡한 질의는 훨씬 더 어렵기는 하지만 여러 노드에서 병렬 실행이 가능하다.
  • p203 키 범위 기준 파티셔닝은 특정한 접근 패턴이 핫스팟을 유발하는 단점이 있다.
  • p203 좋은 해시 함수는 쏠린 데이터를 입력으로 받아 균일하게 분산되게 한다.
  • p204 파티셔닝에 키의 해시값을 사용해서 파티셔닝하면 키 범위 파티셔닝의 좋은 속성을 잃어 버린다. 바로 범위 질의를 효율적으로 실행할 수 있는 능력이다. 전에는 인접했던 키들이 이제는 모든 파티션에 흩어져서 정렬 순서가 유지되지 않는다.
  • p204 복합 키의 첫 번째 컬럼에 값 범위로 검색하는 질의를 쓸 수 없지만, 첫 번째 칼럼에 고정된 값을 지정하면 키의 다른 컬럼에 대해서는 범위 스캔 가능
  • Partitioning Secondary Indexes by Document:
    • p207 각 파티션이 독립적. 자신의 보조 인덱스를 유지하며 그 파티션에 속하는 문서만 담당. 다른 파티션에 어떤 데이터가 저장되는지는 신경 쓰지 않음.
    • p207 보조 인덱스를 써서 읽는 질의는 큰 비용 발생. 여러 파티션에서 질의를 병렬 실행하더라도 꼬리 지연 시간 증폭이 발생하기 쉬움.
  • Partitioning Secondary Indexes by Term:
    • p208 모든 파티션의 데이터를 담당하는 전역 색인을 만들 수도 있음
    • p208 용어 자체로 파티셔닝하면 범위 스캔에 유용함. 반면 용어의 해시값을 사용해 파티셔닝하면 부하가 좀 더 고르게 분산됨.
    • p208 읽기 효율적. 쓰기가 느리고 복잡함.
    • p209 대개 비동기로 갱신됨. 쓰기를 실행한 후 바로 인덱스를 읽으면 변경 사항이 반영되지 않을 수도 있다.
  • Rebalancing Partitions:
    • p209 리밸런싱: 클러스터에서 한 노드가 담당하던 부하를 다른 노드로 옮기는 과정
    • p210 mod 연산 쓰지 마라. 리밸런싱 비용 지나치게 커지고, 데이터를 필요 이상으로 이동하지 않는 방법 필요.
    • p211 파티션 개수 고정: 파티션이 너무 크면 리밸런싱을 실행할 때와 노드 장애로부터 복구 비용이 큼. 그러나 파티션이 너무 작으면 오버헤드가 너무 커짐.
    • p212 동적 파티셔닝: 파티션 개수가 전체 데이터 용량에 맞춰 조정되는 이점. 파티션 개수가 데이터셋 크기에 비례. 데이터 양이 작으면 파티션 개수가 적어도 되므로 오버헤드 적다. HBase, MongoDB는 빈 디비에 초기 파티션 집합 설정(pre-splitting)
    • p213 노드 비례 파티셔닝: 노드 대수가 변함 없는 동안은 개별 파티션 크기가 데이터셋 크기에 비례. 노드 대수 늘리면 파티션 크기 다시 작아짐.
  • 카산드라

07장: 트랜잭션

  • p222 트랜잭션은 전체가 성공(commit)하거나 실패(abort, rollback).

p223 ACID

Atomicity

  • Not
    • In the context of ACID, atomicity is not about concurrency.
    • It does not describe what happens if several processes try to access the same data at the same time, because that is covered under the letter I, for isolation.
  • 여러 쓰기 작업이 하나의 원자적인 트랜잭션으로 묶여 있는데 결함 때문에 완료(commit)될 수 없다면 abort되고 데이터베이스는 이 트랜잭션에서 지금까지 실행한 쓰기를 무시하거나 취소(undo) 해야 한다.
  • Perhaps abortability would have been a better term than atomicity.

Consistency

  • The idea of ACID consistency is that you have certain statements about your data (invariants) that must always be true.
  • it’s the application’s responsibility to define its transactions correctly so that they preserve consistency.
  • The letter C doesn’t really belong in ACID.
    • Atomicity, Isolation, Durability are properties of the database, whereas Consistency (in the ACID sense) is a property of the application.

Isolation

  • In the sense of ACID means that concurrently executing transactions are isolated from each other: they cannot step on each other’s toes.
  • The database ensures that when the transactions have committed, the result is the same as if they had run serially (one after another), even though in reality they may have run concurrently.

transaction-race-conditon

Durability

  • any data it has written will not be forgotten.

Single-Object and Multi-Object Operations

  • atomicity and isolation describe what the database should do if a client makes several writes within the same transaction.
    • Atomicity: If an error occurs halfway through a sequence of writes, the transaction should be aborted, and the writes made up to that point should be discarded. In other words, the database saves you from having to worry about partial failure, by giving an all-or-nothing guarantee.
    • Isolation: Concurrently running transactions shouldn’t interfere with each other. For example, if one transaction makes several writes, then another transaction should see either all or none of those writes, but not some subset.
  • A transaction is usually understood as a mechanism for grouping multiple operations on multiple objects into one unit of execution.

Handling errors and aborts

  • best effort: “the database will do as much as it can, and if it runs into an error, it won’t undo something it has already done”. so it’s the application’s responsibility to recover from errors.
  • the whole point of aborts is to enable safe retries.

p232 Weak Isolation Levels

  • serializable isolation: the database guarantees that transactions have the same effect as if they ran serially (i.e., one at a time, without any concurrency).

Read Committed

  1. When reading from the database, you will only see data that has been committed (no dirty reads).
    • 아직 커밋되지 않은 롤백 데이터 읽을 수 있음 If the database allows dirty reads, that means a transaction may see data that is later rolled back—i.e., which is never actually committed to the database. Reasoning about the consequences quickly becomes mind-bending.
    • 새 값이 커밋돼야만 다른 트랜잭션들이 새 값을 읽을 수 있음. While the transaction is ongoing, any other transactions that read the object are simply given the old value. Only when the new value is committed do transactions switch over to reading the new value.
  2. When writing to the database, you will only overwrite data that has been committed (no dirty writes).

p236-240. Snapshot Isolation and Repeatable Read

  • Read skew is considered acceptable under read committed isolation.
    • skew; timing anomaly.
  • readers never block writers, and writers never block readers.
  • multi-version concurrency control (MVCC)

transaction-multi-version

  • 객체를 볼 수 있는 조건:
    • 읽기 트랜잭션 실행 시점에 이미 커밋된 상태여야함. At the time when the reader’s transaction started, the transaction that created the object had already committed.
    • The object is not marked for deletion, or if it is, the transaction that requested deletion had not yet committed at the time when the reader’s transaction started.

p242~ Preventing Lost Updates

  • The read committed and snapshot isolation levels: 동시 쓰기할 때 read-only 트랜잭션이 무엇을 볼 수 있는지. The guarantees of what a read-only transaction can see in the presence of concurrent writes.
  • If two transactions do this concurrently, one of the modifications can be lost, because the second write does not include the first modification. (later write clobbers the earlier write.)

Atomic write operations

  • MongoDB(document db); atomic operations for making local modifications to a part of a JSON document.
  • Redis; atomic operations for modifying data structures such as priority queues.
  • Atomic operations are usually implemented by taking an exclusive lock on the object.

Explicit locking

  • SELECT **FOR UPDATE**;
  • It’s easy to forget to add a necessary lock somewhere in the code, and thus introduce a race condition.

Compare-and-set

  • 마지막으로 읽은 후로 변경되지 않았을 때만 갱신 허용. To avoid lost updates by allowing an update to happen only if the value has not changed since you last read it.
  • if the database allows the WHERE clause to read from an old snapshot, this statement may not prevent lost updates.
    • because the condition may be true even though another concurrent write is occurring.

Conflict resolution and replication

  • 여러 충돌 버전 생성을 허용하고 사후에 충돌 해소. Allow concurrent writes to create several conflicting versions of a value (also known as siblings), and to use application code or special data structures to resolve and merge these versions after the fact.
  • 최종 쓰기 승리는 갱신 손실 발생하기 쉬움. The last write wins (LWW) conflict resolution method is prone to lost updates. LWW is the default in many replicated databases.

p246 Write Skew and Phantoms

Characterizing write skew

  • 쓰기 스큐는 두 트랜잭션이 다른 객체 갱신. dirty write와 lost update는 다른 트랜잭션이 하나의 동일 객체 갱신. Write skew can occur if two transactions read the same objects, and then update some of those objects (different transactions may update different objects). In the special case where different transactions update the same object, you get a dirty write or lost update anomaly (depending on the timing).
  • 직렬성 격리로 write skew 자동 방지. Automatically preventing write skew requires true serializable isolation.
  • 차선책; serializable isolation 사용할 수 없으면 트랜잭션이 의존하는 로우 잠그기.

Phantoms causing write skew

  • phantom: 쓰기가 다른 트랜잭션의 검색 질의 결과를 바구는 현상. Where a write in one transaction changes the result of a search query in another transaction.

AWS Summit <채널톡의 RDBMS에서 NoSQL 전환> 세션 내용

  • 특정 시점 스파이크 트래픽과 리소스 비효율 문제 해결을 위해 DynamoDB 도입
  • 채팅 뱃지 카운트 트랜잭션 동시성 처리: Optimistic lock 이용해서 conflicts 발생시 exponential backoff

aws-summit-dynamodb

aws-summit-dynamodb

aws-summit-dynamodb

aws-summit-dynamodb

p251 Serializability

  • Testing for concurrency issues is hard, because they are usually nondeterministic — problems only occur if you get unlucky with the timing.
  • the strongest isolation level.
  • It guarantees that even though transactions may execute in parallel, the end result is the same as if they had executed one at a time, serially, without any concurrency.
  • the database prevents all possible race conditions.
  • 2007년경이 돼서야 단일 스레드 루프에서 트랜잭션 실행하는게 실현 가능하다고 결론 내림
    • 램 가격. 모든 데이터를 메모리 적재해서 트랜잭션 빠르게 실행.
    • OLTP 트랜잭션이 보통 짧고 실행하는 읽기와 쓰기 개수가 적음.
  • 단일 스레드 기반 시스템이 동시성을 지원하는 시스템보다 성능이 나을때가 있음. 잠금 오버헤드 피할 수 있기 때문. A system designed for single-threaded execution can sometimes perform better than a system that supports concurrency, because it can avoid the coordination overhead of locking.

Encapsulating transactions in stored procedures

  • 데이터가 모두 메모리에 있고 프로시저는 네트워크나 디스크 I/O 대기 없이 매우 빠르게 실행된다고 가정. Provided that all data required by a transaction is in memory, the stored procedure can execute very fast, without waiting for any network or disk I/O.

transaction-difference

Partitioning

  • 동시성 제어는 간단해지지만 단일 장비의 단일 CPU 코어 속도 제한. Executing all transactions serially makes concurrency control much simpler, but limits the transaction throughput of the database to the speed of a single CPU core on a single machine.

Two-Phase Locking (2PL)

  • 2PL: 쓰기는 다른 쓰기와 읽기 진행하지 못하게 막음. writers don’t just block other writers; they also block readers and vice versa.
  • Snapshot isolation: 읽기와 쓰기 양쪽 모두 막지 못하는 원칙. the mantra readers never block writers, and writers never block.
  • lock: shared mode or in exclusive mode
  • Performance
    • This is partly due to the overhead of acquiring and releasing all those locks, but more importantly due to reduced concurrency.
  • Pessimistic vs. optimistic concurrency control
    • Pessimistic: if anything might possibly go wrong (as indicated by a lock held by another transaction), it’s better to wait until the situation is safe again before doing anything.
    • Optimistic: instead of blocking if something potentially dangerous happens, transactions continue anyway, in the hope that everything will turn out all right.

Performance of serializable snapshot isolation

  • 잠금 때문에 트랜잭션 차단될 필요 없음. Compared to two-phase locking, the big advantage of serializable snapshot isolation is that one transaction doesn’t need to block waiting for locks held by another transaction.
  • The rate of aborts significantly affects the overall performance of SSI. For example, a transaction that reads and writes data over a long period of time is likely to run into conflicts and abort, so SSI requires that read-write transactions be fairly short (long- running read-only transactions may be okay)

08장: 분산 시스템의 골칫거리

  • p274 엔지니어로서의 우리의 임부는 모든 게 잘못되더라도 제 역할을 해내는 시스템을 구축하는것. In the end, our task as engineers is to build systems that do their job (i.e., meet the guarantees that users are expecting), in spite of everything going wrong.
  • Faults and Partial Failures
    • deterministic: ex. 하드웨어가 올바르게 동작하면 같은 연산은 항상 같은 결과를 낸다.
    • partial failure: 분산 시스템에서 시스템의 일부만 고장. nondeterministic
  • p276 분산 시스템이 동작하게 만들려면 partial failure 가능성을 받아들이고 소프트웨어에 내결함성 메커니즘(fault-tolerance mechanisms)을 넣어야 한다. 신뢰성 없는 구성 요소를 사용해 신뢰성 있는 시스템을 구축해야 함. In other words, we need to build a reliable system from unreliable components.
  • Unreliable Networks
    • p279 비동기 네트워크.. 유일한 정보는 응답을 아직 받지 못했다는 것. 응답을 다른 노드로 요청을 보내서 응답을 받지 못했다면 그 이유를 아는 것은 불가능. 이런 문제를 다루는 흔한 방법이 타임아웃.
  • Timeouts and Unbounded Delays
    • 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어짐. 타임아웃이 짧으면 결함을 빨리 발견하지만 노드가 일시적으로 느려졌을 뿐인데도 죽었다고 잘못 선언할 가능성.
    • 2d + r; d: 전송 시간, r: 요청 처리 시간
    • 기약 없는 지연(unbounded delay): 패킷을 가능한 한 빨리 보내려고 하지만 패킷이 도착하는데 걸리는 시간에 상한치는 없다.

Network congestion and queueing

  • p282 TCP 흐름제어(flow control). 노드가 네트워크 링크나 수신 노드에 과부하를 가하지 않도록 자신의 송신율을 제한.
  • TCP는 어떤 타임아웃(왕복 시간을 관찰해서 계산) 안에 확인 응답을 받지 않으면 패킷이 손실됐다고 간주하고 손실된 패킷은 자동으로 재전송. 애플리케이션에게는 패킷 손실이나 재전송이 보이지 않지만 그 결과로 생기는 지연 발생(타임아웃 만료 + 재전송 패킷 확인 응답)
  • p284 고정된 타임아웃을 설정하는 대신 시스템이 지속적으로 응답 시간과 그들의 변동성을 측정, 관찰된 응답 시간 분포에 따라 타임아웃을 자동 조정.
    • Phi Accrual Failure Detector - akka
    • Phi φ Accrual Failure Detection
      1. Suspicion Level (φ value):
        • φ 값은 수신된 하트비트(heartbeat) 사이의 시간 간격을 기반으로 계산. 이 값은 현재 네트워크 상태를 반영하도록 지속적으로 조정. φ 값이 높을수록 노드가 실패했을 가능성이 높다는 것을 의미.
      2. Heartbeat Intervals:
        • 노드로부터 모니터링. interval은 샘플 윈도우에 저장되어 분포를 추정하고 의심 수준을 계산하는 데 사용
      3. Dynamic Thresholds:
        • φ 값은 시스템이 다양한 동적 임계값을 설정. 예를 들어, φ 값이 특정 임계값을 초과하면 노드를 실패한 것으로 간주할 수 있지만, 더 낮은 φ 값에서는 예방 조치를 시작할 수 있다.
    • A → B → C 순으로 호출한다고 하자. A를 관리하고있다면 B에서 지연된건지 C에서 지연된건지 찾는데, 데이터독으로 트레이싱하고있어서, 최근 n일 기준으로 API 의 latency 를 보고. 가령 B→C가 50ms 라면 Read Timeout 고려해서 500ms 정도로 잡고, A→B 구간에서는 500보다 좀 더 크게 잡고.

Synchronous Versus Asynchronous Networks

  • p284 전화 네트워크. 고정 회선.
    • bounded delay(제한 있는 지연): 네트워크의 다음 홉에 통화당 16비트 공간을 미리 할당. 그리고 큐 대기가 없으므로 종단 지연 시간의 최대치가 고정되어 있음.

Can we not simply make network delays predictable?

  • 데이터센터 네트워크와 인터넷은 패킨 교환 사용; bursty traffic 에 최적화
  • 회선; 통화를 하는 동안 보내는 초당 비트 개수가 상당히 고정돼 있는 음성과 영상 통화에 적합
  • 웹 페이지 요청, 이메일 전송, 파일 전송은 특별한 대역폭 요구사항 없음. 단지 가능하면 빨리 완료되기를 바람.

p287 Unreliable Clocks

  • 시간, 시계와 관련된 질문을 Durations(지속 시간), Points in time(시점)에 따라 기술

Synchronized clocks for global snapshots

  • p295 스패너에 대한 이야기(feat. ChatGPT):
    • 데이터베이스 내의 여러 트랜잭션 간의 인과 관계를 고려하여 타임스탬프를 부여
      • 트랜잭션 타임스탬프: 각 트랜잭션이 완료된 시점(시간). 트랜잭션의 순서를 정하고, 데이터의 일관성을 유지하는 데 중요
      • 인과성(Causality): 한 트랜잭션이 다른 트랜잭션에 영향을 미치는 관계. 예를 들어, 트랜잭션 A가 트랜잭션 B보다 먼저 실행되고, B가 A의 결과에 의존하는 경우, A는 B의 원인이 된다. 1. TrueTime API:
      • TrueTime API를 사용하여 각 트랜잭션에 정확한 타임스탬프를 부여. TrueTime은 GPS 및 원자시계 정보를 활용하여 매우 정확한 시각 정보를 제공.
      • TrueTime의 사용으로 인해 각 트랜잭션의 완료 시점에 대해 상한(earliest) 및 하한(latest) 시간을 알 수 있으며, 이를 통해 트랜잭션 간의 정확한 순서를 결정. 2. 트랜잭션 순서 보장:
      • 트랜잭션 간의 인과 관계를 보장하기 위해, 이전 트랜잭션이 완료된 후에만 다음 트랜잭션을 수행. 이는 인과 관계가 반영된 타임스탬프를 부여하는데 중요한 역할을 한다. 3. Globally Consistent Reads and Writes:
      • 이러한 타임스탬프 메커니즘 덕분에, Spanner는 전 세계에 분산된 데이터베이스에서도 강력한 일관성을 유지할 수 있습니다. 각 트랜잭션의 타임스탬프가 인과성을 반영하기 때문에, 사용자는 일관된 데이터를 읽고 쓸 수 있습니다.
    • Spanner: TrueTime and external consistency
    • Spanner: Google’s Globally-Distributed Database

Process Pauses

  • note) p297. 실행 중인 스레드를 어떤 시점에 선점(preempt)하고 얼마만의 시간이 흐른후 재개할 수 있다. 선점된 스레드는 이를 알아채지 못한다. 이 문제는 단일 장비에서 다중 스레드 코드를 thread-safe 하게 만드는 것과 비슷. 컨텍스트 스위치가 임의로 발생할 수 있고 병렬(parallelism)이 발생할 수 도 있으므로 타이밍에 대해 어떤 가정도 할 수 없다.

Knowledge, Truth, and Lies

  • p300 인식하고 측정하는 수단을 믿을 수 없다면 그 지식을 어떻게 확신할 수 있나? How sure can we be of that knowledge, if the mechanisms for perception and measurement are unreliable?

The Truth Is Defined by the Majority

  • p301 노드가 상황에 대한 자신의 판단을 반드시 믿을 수 있는 것은 아님. 분산 시스템은 한 노드에만 의존할 수는 없다. 노드에 언제든 장애가 나서 잠재적으로 시스템이 멈추고 복구할 수 없게 될 수도 있기 때문이다. 정족수(quorum)

Fencing tokens

  • p303 잠금이 승인될 때마다 증가하는 숫자.

fencing-token

  • 자원 자체가 이미 처리된 것보다 오래된 토큰을 사용해서 쓰는 것을 거부함으로써 토큰을 확인하는 활동적인 역할을 맡아야 함.

Byzantine Faults

  • p304 어떤 노드가 실제로는 받지 않은 특정 메시지를 받았다고 주장…
  • 비잔틴 결함(Byzantine fault), 비잔틴 장군 문제(Byzantine Generals Problem)
    • 메시지 전달 전략과 두 장군 문제(Message Delivery Semantics and Two Generals’ Problem)
    • ELC: SpaceX lessons learned
      • SpaceX에서는 Falcon, Dragon, Grasshopper 등의 비행 제어, 지상 스테이션, 개발자 데스크톱까지 모든 것이 리눅스.
      • fault-tolerant(내결함성/결함 허용)을 위해 3중으로 시스템을 구성하고 비잔틴 장군 알고리즘을 사용. 우주 정거장(ISS)에 접근할 때, 결함 허용 수준을 만족해야만 정거장에 접근할 수 있음. 이를 위해 삼중 중복 컴퓨터를 사용해서 필요한 수준의 결함 허용을 달성.
      • 비잔틴 장군 알고리즘은 컴퓨터들이 동의하지 않는 상황을 처리하는 데 사용. 이런 상황은 방사선 사건으로 인해 메모리나 레지스터 값이 변경되는 경우 발생.
  • Byzantine fault-tolerant : 일부 노드가 오작동하고 프로토콜을 준수하지 않거나 악의적인 공격자가 네트워크를 방해하더라도 시스템이 계속 올바르게 동작
  • 보통 비잔틴 결함이 없다고 가정할 수 있다. 우리 조직이 모든 노드 제어.. 방사선 수준은 큰 문제 아님..

Safety and liveness

  • p308 안전성(나쁜 일은 일어나지 않는다), 활동성(좋은 일은 결국 일어난다). Safety is often informally defined as nothing bad happens, and liveness as something good eventually happens.

정리

  • 결함을 견뎌내려면 그것을 감지하는 게 첫 걸음이지만 그것조차 어렵다.
  • 대부분의 시스템은 노드에 장애가 발생했는지 알 수 있는 정확한 메커니즘이 없어서 대부분의 분산 알고리즘은 원격 노드를 아직 쓸 수 있는지 결정하기 위해 타임아웃을 사용.
  • 그러나 타임아웃은 네트워크 장애와 노드 장애를 구별할 수 없고 변동이 큰 네트워크 지연 때문에 때때로 노드가 죽은 것으로 잘못 의심받을 수 있다.

09장: 일관성과 합의(~p.349)

p.321 Consistency Guarantees

  • 약한 보장(weak guarantee): 복제 데이터 베이스는 대부분 최소한 최종적 일관성(eventual consistency**)을 제공한다. 하지만 언제 **수렴(convergence) 될 지 모름.
  • 강한 보장(strongest consistency): 데이터 시스템이 선택적으로 제공. 성능이 나쁘거나 약한 보장을 제공하는 시스템보다 내결함성이 약할 수 있음.
  • 트랜잭션 격리 수준과의 차이점;
    • 트랜잭션 격리: 주로 동시에 실행되는 트랜잭션 때문에 발생하는 경쟁 조건을 회피하는 것에 관한 것.
    • 분산 일관성: 대개 지연과 결함이 있더라도 복제본의 상태를 코디네이션하는 것

p322 Linearizability

  • “데이터베이스가 복제본이 하나만 있다면 훨씬 더 단순해 지지 않을까?” 원자적 일관성(atomic consistency), 강한 일관성(strong consistency), 즉각 일관성(immediate consistency), 외부 일관성(external consistency)

consistency

  • 최신성 보장(recency guarantee)

시스템에 선형성을 부여하는 것

  • *cas*(*x*, *v*old, *v*new) ⇒ *r*:
    • 클라이언트가 cas(compare-and-set) 연산 요청
    • 레지스터 x의 현재 값이 Vold과 같으면 Vnew로 설정하고 아니면 오류 발환(error)

linearizability

  • p332 요약; 다이나모 스타일 복제를 하는 리더 없는 시스템은 선형성을 제공하지 않는다고 보는 게 안전.

p333 선형성의 비용

  • 단일 리더 설정에서 데이터센터 사이에 연결 끊기면 선형성 읽기 불가능. 팔로워로부터 읽을 수는 있지만 데이터가 뒤쳐졌을 수 있다(비선형적).

p335 CAP

  • 네트워크 결함이 생기면 선형성과 완전한 가용성 사이에서 선택해야 함. CAP는 네트워크 분단이 생겼을때 일관성과 가용성 중 하나를 선택하라는 의미.

p342 비인과적 일련번호 생성기(Noncausal sequence number generators)

  • 노드별) 각 노드 개별로 독립적인 일련번호 집합 생성. 예) 한 노드는 홀수만, 다른 노드는 짝수만 생성.
  • 잘 동작하지만 생성한 일련번호가 인과성에 일관적이지 않음(timestamp 불일치)

p343 Lamport timestamps

  • <카운터, 노드> ID 조합.
  • 때때로 카운터 값이 같을 수 있지만 타임스탬프에 노드 ID를 포함시켜서 각 타임스탬프 유일.
  • 물리적 일 기준 시계와 아무 관련이 없지만 전체 순서화를 제공.
  • 버전 벡터와의 차이;
    • 버전 벡터: 두 연산이 동시적인지 또는 어떤 연산이 다른 연산에 인과적으로 의존하는지 구별
    • 램포트 타임스탬프: 항상 전체 순서화를 강제화

Total Order Broadcast

  • p346 후속 메시지가 이미 전달됐다면 노드는 그 순서의 앞에 메시지를 소급적으로 끼워넣는 게 허용되지 않는다. 전체 순서 브로드캐스트가 타임스탬프 순서화보다 강하다.
  • p347 선형성과의 차이
    • 전체 순서 브로드캐스트는 비동기식. 메시지는 고정된 순서로 신뢰성 있게 전달되도록 보장. 하지만 언제 메시지가 전달되는지 보장되지 않음.
    • 선형성은 최신성 보장. 읽기가 최근에 쓰여진 값을 보는 게 보장.

09장: 분산 트랜잭션과 합의(p.349~)

p349 Consensus

  • 합의: 여러 노드들이 뭔가에 동의하게 만드는 것
  • 리더 선출(Leader election): 단일 리더 복제를 사용하는 DB에서 모든 노드는 어떤 노드가 리더인지 동의해야 함.
  • 원자적 커밋(Atomic commit): 트랜잭션 원자성을 유지하고 싶다면 모든 노드가 트랜잭션의 결과에 동의해야 함.

p351 원자적 커밋과 2PC(Two-Phase Commit) why?

  • 트랜잭션의 결과는 항상 커밋 Success 이거나 Abort. The outcome of a transaction is either a successful commit, in which case all of the transaction’s writes are made durable, or an abort, in which case all of the transaction’s writes are rolled back (i.e., undone or discarded).
  • 트랜잭션에 여러 노드가 관여하게 되면 어떤 노드에서는 커밋이 성공하고 다른 노드에서는 실패해서 원자성 보장을 위반하기 쉽다. 어떤 노드가 트랜잭션을 Commit 하지만 다른 노드는 Abort 한다면 노드들이 서로 일관성이 없어진다.
  • 트랜잭션 커밋은 되돌릴 수 없어야 한다. 보상 트랜잭션(compensating transaction)은 취소 가능하지만 분리된 트랜잭션 개념으로 이해해야 한다.

p.352-355 2PC

  • 2PC는 여러 노드에 걸친 원자적 트랜잭션 Commit 되게 함. 즉 모든 노드가 커밋되거나 모든 노드가 Abort 되도록 보장하는 알고리즘

2pc

  • 코디네이터(coordinator, transaction manager), 데이터베이스 노드들을 트랜잭션 참여자(participant)
  • 애플리케이션이 Commit할 준비가 되면 코디네이터가 1단계 시작 → 각 노드에 준비 요청을 보내서 커밋할 수 있는지 물어본다 → 그 후 코디네이터는 참여자들의 응답을 추적
    • 모든 참여자가 커밋할 준비가 됐다면 “yes” 응답 → 코디네이터는 2단계에서 커밋 요청(commit request)하고 실제로 커밋 발생
    • 참여자 중 누구라도 “no” 응답 → 코디네이터는 2단게에서 모든 노드에 Abort 요청
  • 코디네이터 장애시, 2PC가 완료될 수 있는 유일한 방법은 코디네이터가 복구되기를 기다리는것 뿐.
    • 코디네이터가 Commit 이나 Abort 요청 보내기 전에 디스크에 있는 트랜잭션 로그에 기록
    • 코디네이터가 복구될 때 트랜잭션 로그를 읽어서 모든 의심스러운 트랜잭션들의 상태를 결정

p358-360 XA transactions

  • 트랜잭션 코디네이터와 연결되는 인터페이스를 제공하는 API. 트랜잭션 코디네이터는 XA API를 구현.
  • 애플리케이션이 네트워크 드라이버나 클라이언트 라이브러리를 사용해 참여자 DB나 메시징 서비스와 통신한다고 가정.
  • 애플리케이션 프로세그가 죽거나 애플리케이션이 실행중인 장비가 죽으면 코디네이터도 함께 사라짐
  • 코디네이터 장애 복구 문제. 유일한 방법은 관리자가 수동으로 트랜잭션을 커밋하거나 롤백할지 결정하는 것.
  • JDBC 분산 트랜잭션

    JDBC(Java Database Connectivity)의 트랜잭션은 로컬. 이는 단일 연결이 트랜잭션의 모든 작업을 수행하며 연결은 한 번에 하나의 트랜잭션에 대해서만 작업할 수 있음을 의미.

    하나의 트랜잭션에 대한 모든 작업이 완료되거나 실패한 경우, 작업을 영구적으로 만들기 위해 커밋 또는 롤백이 호출되고 새 트랜잭션이 시작된다. 그러나 Java에서는 로컬 트랜잭션을 넘어서는 기능을 제공하는 고급 트랜잭션 지원도 있고, Java 트랜잭션 API에서 제공한다.

    Java Transaction API(JTA)는 복잡한 트랜잭션을 지원. 또한 연결 객체에서 트랜잭션을 분리하는 기능도 제공합니다. JDBC가 객체 데이터베이스 연결(ODBC) 및 X/Open 호출 수준 인터페이스(CLI) 명세를 모델로 삼았듯이, JTA는 X/Open eXtended Architecture(XA) 명세를 모델로 삼습니다. JTA와 JDBC는 연결 객체에서 트랜잭션을 분리하기 위해 함께 작동. 연결 객체에서 트랜잭션을 분리함으로써 단일 연결이 여러 트랜잭션을 동시에 처리할 수 있다. 반대로 여러 연결이 단일 트랜잭션을 처리할 수도 있음.

p360 분산 트랜잭션의 제약

  • XA 트랜잭션은 여러 참여 데이터 시스템이 서로 일관성을 유지하게 하는 실제적이고 중요한 문제를 해결해 주지만, XA 트랜잭션도 중요한 운영상 문제를 가져온다.
  • 핵심 구현은 트랜잭션의 코디네이터 자체가 일종의 데이터베이스여야 하고, 따라서 다른 중요한 데이터베이스와 동일하게 신경 써서 접근해야함.
    • 분산 트랜잭션은 장애를 증폭시키는 경향이 있으며 이는 내결함성을 지닌 시스템을 구축하려는 목적에 어긋난다.

p361 내결함성을 지닌 합의(Fault-Tolerant Consensus)

p365 에포크 번호 붙이기와 정족수

  • 현재 리더가 죽었다고 생각될 때마다 새 노드를 선출하기 위해 노드 사이에서 투표를 해서 에포크 번호가 높은 리더가 이겨서 리더가 된다.
  • 리더가 뭔가를 결정하도록 허용하기 전에 충돌되는 결정을 할 지도 모르는 에포크 번호가 더 높은 리더가 없는지 먼저 확인해야 한다.
  • 노드의 정족수로부터 투표를 받아서 리더를 판단합니다. 정족수는 항상은 아니지만 노드의 과반수로 구성된다.
  • 2PC는 “yes” 투표가 필요하지만, 내결함성을 지닌 합의 알고리즘은 노드의 과반수로부터만 투표.

p367 멤버십과 코디네이션 서비스

  • HBase, 하둡, 노바, 카프카는 모두 주키퍼에 의존.
  • 주키퍼 기능
    • 선형적 원자적 연산(Linearizable atomic operations): 원자적 compare-and-set 연산을 사용해 잠금을 구현
    • 연산의 전체 순서화(Total ordering of operations): 펜싱 토큰 등을 사용해 클라이언트 충돌 막기
    • 장애 감지(Failure detection): 세션에서 획득한 잠금은 세션이 타임아웃 됐을 때 자동으로 해제되도록 설정
    • 변경 알림(Change notifications): 클라이언트는 다른 클라이언트가 생성한 잠금과 값을 읽을 수 있을 뿐만 아니라 거기에 변경이 있는지 감시할 수 있다.

p369 Service discovery, Membership services

  • Service discovery: 특정 서비스에 연결하려면 어떤 IP 주소로 접속해야 하는지 알아내는 용도로도 자주 사용.
  • Membership services: 클러스터에서 어떤 노드가 현재 활성화된 살아 있는 멤버인지 결정.

11장: 스트림 처리(~p453)

p382 레코드 시스템과 파생 데이터 시스템

레코드 시스템(Systems of record) 파생 데이터 시스템(Derived data systems)
믿을 수 있는 데이터 버전을 저장 다른 시스템에 존재하는 데이터를 가져와 특정 방식으로 변환하고 처리한 결과
진실의 근원(source of truth). 사용자의 입력과 같은 새로운 데이터가 들어오면 먼저 레코드 시스템에 저장 데이터를 잃게 되더라도 원천 데이터로부터 다시 생성 가능
일반적으로 정규화를 거쳐 정확하게 한 번 표현된다. 레코드 시스템과 다른 시스템 간에 차이가 난다면 레코드 시스템이 옳다. 필요한 데이터가 캐시에 있다면 캐시에서 제공하고, 그렇지 않다면 기반 데이터베이스를 거쳐 제공할 수 있다.

p436 이벤트 스트림 전송(Transmitting Event Streams)

  • 이벤트
    • 스트림 처리 문맥(stream processing context)에서 레코드는 보통 이벤트라고 한다.
    • 특정 시점에 일어난 사건에 대한 세부 사항을 포함하는, 작고 독립된 불변 객체라는 점에서 본질적으로 동일하다.
  • 생성자(producer)와 소비자(consumer)
    • 생산자(producer), 발행자(publisher), 발송자(sender)
    • 소비자(consumer), 구독자(subscriber), 수신자(recipient)
    • 레코드 식별(그룹핑)
      • 파일 시스템: 파일 이름으로 관련 레코드 식별
      • 스트림 시스템: 토픽(topic)이나 스트림으로 관련 이벤트 그룹핑

메시징 시스템 p438

  • 메시지 시스템에 대한 질문(Publish, Subscribe 모델)

    Q 소비자가 메시지 처리하는 속도보다 생산자가 메시지 전송하는게 빠른 경우(생산자 > 소비자)

    • 메시지를 버리기
    • 큐에 메시지 버퍼링하기
    • 배압(backpressure) 적용:
      • 생산자가 메시지를 더 보내지 못하게 막기
      • 유닉스 파이프와 TCP가 사용

    Q 노드가 죽거나 일시적으로 오프라인이 된다면?

    • 디스크에 기록하거나 복제본 생성을 하거나 둘 모두를 해야 한다.
    • 메시지를 잃어도 괜찮다면 같은 하드웨어에서 처리량은 높이고 지연 시간은 낮출 수 있다.
  • 생산자에서 소비자로 메시지 직접 전달: 프로토콜이 네트워크 상에서 패킷 유실을 감지하고 재전송하더라도 직접 메시지 시스템은 일반적으로 생산자와 소비자가 항상 온라인 상태라고 가정
  • p440 메시지 브로커와 데이터베이스 비교

    데이터베이스(Databases) 메시지 브로커(Message brokers)
    명시적으로 데이터가 삭제될 때까지 데이터 보관 대부분 소비자에게 데이터 배달이 성공할 경우 자동으로 메시지 삭제. 대부분 메시지를 빨리 지우기 때문에 작업 집합이 상당히 작다고 가정(큐 크기가 작다). 소비자 처리가 느려서 메시지 브로커가 많은 메시지를 버퍼링해야 한다면, 개별 메시지 처리 시간이 길어지고 전체 처리량이 저하됨
    보조 색인을 지원. 데이터 검색을 위한 다양한 방법 지원 특정 패턴과 부합하는 토픽의 부분 집합을 구독하는 방식 지원
    일반적으로 질의 시점의 데이터 스냅숏을 기준으로 질의 임의 질의를 지원하지 않음. 데이터가 변하면 클라이언트에게 전달
  • p441 복수 소비자(Multiple consumers)
    • 로드 밸런싱(Load balancing):
      • 각 메시지는 소비자 중 하나로 전달
      • 메시지를 처리하는 비용이 비싸서 처리를 병렬화하기 위해 소비자를 추가하고 싶을때 유용
    • 팬 아웃(Fan-out):
      • 각 메시지는 모든 소비자에게 전달
      • 여러 독립적인 소비자가 브로드캐스팅된 동일한 메시지를 서로 간섭 없이 리스닝 가능

fanout

p442 확인 응답과 재전송(Acknowledgments and redelivery)

  • 생산자: m1 → m2 → m3 → m4 → m5
  • 소비자2가 m3 처리중 장애 발생해서 소비자1로 m3 메시지 재전송.
  • 소비자 1에서는 m4 → m3 → m5 순으로 메시지 처리

consumer

  • 소비자마다 독립된 큐를 사용하면, 즉 부하 균형 분산 기능을 사용하지 않는다면 이 문제를 피할 수 있다.
    • 카프카 토픽의 파티션 키(해시) - stickness
  • 메시지가 서로 완전히 독립적이라면 메시지 순서가 바뀌는 것은 문제가 되지 않는다.

p443 파티셔닝된 로그(Partitioned Logs)

로그 기반 메시지 브로커(log- based message brokers): 데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 조합

  • 로그를 사용한 메시지 저장소

    로그 == 파티션

    • p445 로그 파티셔닝(the log can be partitioned): 디스크 하나를 쓸 때보다 처리량을 높이기 위해 확장하는 방법

      • 하나의 파티션을 처리하는 작업을 공유하는 소비자 둘로 나누어 로드 밸런싱하는 방식을 사용할 수 있다. 양쪽 소비자 모두 모든 메시지를 읽지만 그 중 하나는 짝수 오프셋 메시지만 처리하고, 다른 소비자는 홀수 오프셋 메시지만 처리하는 방식.
      • 스레드 풀 사용을 사용해 메시지 처리 분산하는 방식. 소비자 오프셋 관리가 복잡해진다. 일반적으로 한 파티션은 단일 스레드가 처리하는 것이 적절하고, 병렬성을 높이고 싶다면 파티션 수를 늘리는 게 좋다.
    • 단 다른 파티션 간 메시지의 순서는 보장하지 않는다.

partition

  • p444 로그 방식과 전통적인 메시징 방식의 비교

    로그 기반 접근법은 팬 아웃 메시지 방식 제공:

    • 소비자가 서로 영향 없이 독립적으로 로그를 읽을 수 있고 메시지를 읽어도 로그에서 삭제되지 않는다.

    각 클라이언트는 할당된 파티션의 메시지를 모두 소비:

    • 토픽 하나를 소비하는 작업을 공유하는 노드 수는 많아야 해당 토픽의 로그 파티션 수로 제한. 같은 파티션 내 메시지는 같은 노드로 전달되기 때문.
    • 특정 메시지 처리가 느리면 파티션 내 후속 메시지 처리가 지연된다(head-of-line blocking).
    메시지 처리 비용 비싼 경우, 메시지 단위로 병렬화 처리 하고 싶은 경우, 메시지 순서는 그렇게 중요하지 않은 경우 JMS / AMQP 방식의 메시지 브로커 적합
    처리량이 많은 경우, 메시지를 처리하는 속도가 빠른 경우, 메시지 순서가 중요한 경우 로그 기반 접근법 적합
  • p445 소비자 오프셋(Consumer offsets)
    • 브로커는 주기적으로 소비자 오프셋을 기록
  • p446 디스크 공간 사용
    • 로그는 크기가 제한된 버퍼로 구현하고, 버퍼가 가득 차면 오래된 메시지 순서대로 버린다.
    • 원형 버퍼(circular buffer), 링 버퍼(ring buffer)

데이터베이스와 스트림

  • p448 데이터베이스에 뭔가를 기록한다는 사실은 캡처해서 저장하고 처리할 수 있는 이벤트다.
  • p448 시스템 동기화 유지하기
  • 이중 기록(dual write)의 심각한 문제
    • 문제 1) 타이밍이 좋지 않아 요청이 서로 교차한 경우, 두 시스템은 오류가 발생하지 않았음에도 영원히 서로 불일치 상태로 남음.
    • 문제2) 한쪽 쓰기가 성공할 때, 다른쪽 쓰기는 실패할 수 있다. 동시성 문제라기보다는 내결함성 문제로 두 시스템 간 불일치가 발생하는 현상.

dual-write

  • p450 변경 데이터 캡처(Change Data Capture)
    • 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터를 복제할 수 있는 형태로 추출하는 과정

cdc

  • 파생 데이터 시스템이 레코드 시스템의 정확한 데이터 복제본을 가지게 하기 위해, 레코드 시스템에 발생하는 모든 변경 사항을 파생 데이터 시스템에 반영하는 것을 보장하는 메커니즘
  • 멕스웰(maxwell), 디비지움(debezium): binlog를 파싱해 유사한 방식으로 mysql용 변경 데이터 캡처를 구현

  • p452 초기 스냅숏(Initial snapshot):
    • 데이터베이스에서 발생한 모든 변경 로그가 있다면, 로그를 재현해서 데이터베이스의 전체 상태를 재구축 가능
  • p452 로그 컴팩션(Log compaction):
    • 저장 엔진은 주기적으로 같은 키의 로그 레코드를 찾아 중복을 제거
    • 각 키에 대해 가장 최근에 갱신된 내용만 유지
  • p453 변경 스트림용 API 지원
    • kafka connect;
      • 변경 이벤트를 스트림하는데 카프카 사용
      • 검색 색인과 같은 파생 데이터 시스템을 갱신하는데 사용 가능
      • 스트림 처리 시스템에도 이벤트 공급이 가능

11장: 스트림 처리(p454~11장 끝)

이벤트 소싱(event sourcing)

  • CDC:
    • 데이터베이스를 변경 가능한 방식으로 사용해 레코드를 자유롭게 갱신하고 삭제
  • 이벤트 소싱:
    • 이벤트 로그에 기록된 불변 이벤트를 기반으로 명시적으로 구축
    • 이벤트 저장은 추가만 가능하고 갱신이나 삭제는 권장하지 않거나 금지
    • 어떤 상황이 발생한 후에 상황 파악이 쉽기 때문에 디버깅에 도움이 되고 버그 방지

이벤트 로그에서 현재 상태 파생하기

  • 시스템에 기록한 데이터를 표현한 이벤트 로그를 가져와 사용자에게 보여주기에 적당한 애플리케이션 상태(시스템에서 데이터를 읽는 방식)로 변환해야 한다.
    • 예시; 현재 장바구니 내용(변경 사항은 관심사 x)
  • CDC: 레코드의 가장 새로운 버전만 보유. 같은 키의 이전 이벤트는 로그 컴팩션을 통해 버린다
  • 이벤트 소싱: 뒤에 발생한 이벤트가 앞선 이벤트를 덮어쓰지 않는다. 마지막 상태 재구축을 위해서는 이벤트의 전체 히스토리가 필요하다.

p455 명령과 이벤트

  • 명령이 실행 가능한지 확인.
  • 무결성이 검증되고 명령이 승인되면 명령은 지속성 있는 불변 이벤트가 된다.
  • 명령의 유효성은 이벤트가 되기 전에 동기식으로 검증해야 한다.
    • 좌석 예약 예시.

p456 상태와 스트림 그리고 불변성

  • 상태의 본질을 변하는 것이다. 상태가 어떻게 바뀌었든 항상 이런 변화를 일으킨 일련의 이벤트가 있다.
  • 변경 가능한 상태와 추가 전용 불변 이벤트 로그는 마치 동전의 양면과 같이 서로 모순이다. 모든 변경 로그(changelog)는 시간이 지남에 따라 바뀌는 상태를 나타낸다.
  • 변경 로그를 지속성 있게 저장한다면 상태를 간단히 재생성할 수 있는 효과가 있다. 이벤트 로그를 레코드 시스템으로 생각하고 모든 변경 가능 상태를 이벤트 로그로부터 파생된 것으로 생각하면 시스템을 거치는 데이터 흐름에 관해 추론하기 쉽다.

p458 동일한 이벤트 로그로 여러 가지 뷰 만들기

  • CQRS(Command Query Responsibility Segregation)
  • 읽기 최적화된 뷰(read-optimized views)는 데이터를 비정규화하는 것이 전적으로 합리적이다.

동시성 제어

  • 이벤트가 아직 읽기 뷰에 반영되지 않았을 가능성

p460 불변성의 한계

  • 성능적인 이유 외에도. 사생활 침해 규제로 인해 개인 정보 지울 필요가 있다든지, 데이터 보호법에 따라 잘못된 정보를 삭제해야 한다든지, 민감한 정보가 우발적으로 노출되는 것을 방지해야 하는 경우

p461 스트림 처리

  • 몇 분 동안 실행된 일괄 처리 작업은 실패한 태스크를 처음부터 재시작하는 것으로 충분, 하지만 몇 년 동안 실행 중인 스트림 작업은 장애 발생 이후 처음부터 재시작하는 방법은 비현실적.

p462 복잡한 이벤트 처리

  • CEP(Complex event processing): 특정 이벤트 패턴을 검색해야 하는 애플리케이션에 적합
  • 스트림 분석(Stream analytics): 연속한 특정 이벤트 패턴을 찾는 것보다 대량의 이벤트를 집계하고 통계적 지표를 뽑는 것을 더 우선.
    • 특정 유형의 이벤트 빈도 측정(시간당 얼마나 자주 발생하는지)
    • 특정 기간에 걸친 값의 이동 평균(ROLLING AVERAGE) 계산
    • 이전 시간 간격과 현재 통계값의 비교(추세 감지, 지난주 대비 비정상적으로 높거나 낮은 지표에 대해 경고)

p464 구체화 뷰(materialized views) 유지하기

  • 이벤트 소싱에서 애플리케이션 상태는 이벤트 로그를 적용함으로써 유지된다. 여기서 애플리케이션 상태는 일종의 구체화 뷰다.
  • 구체화 뷰를 만들려면 잠재적으로 임의의 시간 범위에 발생한 모든 이벤트가 필요하다.

p464 스트림 상에서 검색하기

  • 질의를 먼저 저장.

메시지 전달과 RPC

메시지 전달이 stream 처리, RPC는 액터. 액터는 1:1로 메시지 주고 받는 형태. 스트림 처리는 카프카처럼 데이터 파이프라인 구축. 스트림 처리는 메시지 관심있는 사람 여러명이 관심. 1:1 / 1:n 또는 n:m

  • 액터:
    • 주로 동시성 관리, 통신 모듈을 분산 실행하는 매커니즘
    • 액터간 통신은 주로 단기적이고 일대일
    • 임의의 방식으로 통신 가능
  • 스트림 처리:
    • 기본적으로 데이터 관리 기법
    • 지속성 있고 다중 구독 가능
    • 비순환 파이프라인에 설정됨.

p468 어떤 시계를 사용할 것인가?

잘못된 장치 시계를 조정하는 한 가지 방법: 세 가지 타임스탬프를 로그로 남기기

  1. 이벤트가 발생한 시간 - 장치 시계
  2. 이벤트를 서버로 보낸 시간 - 장치 시계
  3. 서버에서 이벤트를 받은 시간 - 서버 시계

2와 3 차이를 구하면 장치 시계와 서버 시계 간의 오프셋 추정 가능. 계산한 오프셋을 이벤트 타임스탬프에 적용해 이벤트가 실제로 발생한 시간을 추정.

p469 윈도우 유형

  • 텀블링 윈도우(Tumbling window)
  • 홉핑 윈도우(Hopping window)
  • 슬라이딩 윈도우(Sliding window)
  • 세션 윈도우(Session window)

p474 내결함성(Fault Tolerance)

  • 태스크를 재시작하는 것은 사실 레코드를 여러 번 처리할 수도 있다는 뜻. 출력은 한 번만 처리된 것으로 보이는 효과.
    • exactly-once semantics(정확히 한 번 시맨틱), effectively-once(결과적으로 한 번)
  • Microbatching and checkpointing(마이크로 일괄 처리와 체크포인트)
  • Atomic commit revisited(원자적 커밋 재검토)
  • 멱등성(Idempotence)
  • 실패 후에 상태 재구축하기(Rebuilding state after a failure)