2020-09-16

데이터 중심 어플리케이션 설계 갈무리

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

  • 많은 애플리케이션은 계산보다는 데이터 중심적이다.
  • 데이터 중심적인 애플리케이션은 아래 5개

    • 저장하기 위한 데이터베이스
    • 읽기 속도 향상을 위한 캐시
    • 검색이나 필터를 위한 색인
    • 비동기를 위한 메시지 스트림 처리
    • 누적된 데이터를 분석하는 배치 처리
  • 데이터 저장도구간 경계가 흐려지고 있다. 카프카도 지속성(durability) 를 보장하고, 레디스도 메시지 큐로 쓸 수 있는 것 처럼.
  • 소프트웨어 중요한 3가지 관심사 ⇒ 신뢰성, 확장성, 유지보수성

    • 신뢰성
    • 기능 수행, 예기치 못한 사용 허용
    • 부하에서의 작동
    • 허가되지 않은 접근과 오남용 방지
    • 확장성
    • 부하가 늘어도 동작해야 한다.
    • 트위터 사례

      • 트윗피드는 select 문에서, 메시지를 통해 캐시를 업데이트 하는 구조로 진화했다.
      • 한명이 글쓰면 팔로워들의 피드 캐시들을 다 업데이트 해주는 구조.
    • 성능 기술에는 처리량(throughput)과 응답시간(response time).

      • 데이터 처리에는 쓰루풋을 보고, 온라인 기능에는 응답시간을 주로 본다.
      • 아마존 응답시간이 100밀리초 늘면 판매량이 1%, 1초가 느려지면 지표는 16% 줄어들었다.
    • 확장은 크게 scaling up( = vertical scaling) 과 scaling out(horizontal scaling) 으로 나눈다.
    • 부하가 늘어남에 따라 컴퓨팅 자원을 늘릴 수 있다면 탄력적(elastic) 이라고 한다.
    • stateless 서비스를 배포하고 늘리는 것은 쉽지만, stateful 은 복잡도가 많이 는다.
    • 유지보수성
    • 운용성: 운영팀이 시스템을 운영하게 쉽게

      • 모니터링, 성능저하 원인파악, 보안패치, 플랫폼 최신상태 유지
      • 미래 발생가능한 문제 예측
      • 배포, 설정 관리,
      • 애플리케이션 이동하는 등의 마이그레이션
      • 조직 지식을 보존
    • 단순성: 복잡도 제거하고 새로운 엔지니어가 시스템을 이해하기 쉽게

      • 복잡도는 예산과 일정측정에 어려움을 줌.
      • 복잡도 증가는, 상태의 급증, 모듈 강결함,
      • 복잡한 의존성 , 일관성 없는 네이밍, 성능문제 해결을 위한 몽키패칭, 임시방편 코드들 이 시그널
      • 복잡도 제거를 위한 추상화가 최상의 도구이나 좋은 추상하는 달성하기 매우 어려움. 이 책에서 살펴보자.
    • 발전성: 요구사항 변경등에 대응하여 쉽게 변경할 수 있게

      • TTD, 애자일
      • 로컬 규모(동일 어플리캐이션 내에 소스코드 파일 수 )
      • 리팩토링

02. 데이터 모델과 질의 언어

  • 어플리케이션 객체와 데이터베이스 모델(테이블,컬럼,로우) 사이에 전환계층이 필요하고, 이런 분리를 impedance mismatch 라고 부른다.
  • JSON 표현에서는 관련 정보가 한곳에 있어 질의 하나로 충분하다.
  • NOSQL 에서 join 흉내를 내는 어플리케이션 코드가 필요하기도 하다.
  • 다대일이나 다대다 관계를 표현할 때, 관계형이나 문서형 둘다 고유 식별차를 참조한다. 크게 다르지 않다.
  • 문서,관계,그래프 모두 널리 사용되고 있다. 한 모델로 다른 모델을 흉내낼 수 있긴 하지만 그 결과는 대부분 엉망이다.

04 부호화의 발전

  • 새로운 코드는 예전코드가 기록한 데이터를 읽을 수 있어야 하며, 그 반대도 마찬가지다. 상위호환성 + 하위호환성
  • 네트워크를 통해 전송하려면 바이트열 형태로 부호화해야한다. 이를 부호화 (직렬화 혹은 마샬링)이라고 한다.
  • 데이터는 다양한 시점에 기록된다. 데이터는 코드보다 더 오래 산다.
  • RPC 의 문제

    • 로컬 함수는 결국 아니기 때문에, 네트워크 요청이 가지고 있는 문제점을 가진다.
    • 응답없이 timeout 이 날 경우 어떠한 일이 일어난지 알 수 없다.
    • 실패 요청에 대해 다시 요청할 경우, 명등성이 보장되지 않으면 재시도시 문제가 될 수 있다.
    • 네트워크 요청은 느리다.
    • 로컬은 결국 포인터나 메모리 상황을 알 수 있지만, 네트워크는 못한다.
    • restful api 는 코드생성이나 설치 없이 curl 로 간단하게 디버깅 가능하지만 rpc 는 아니다.

07 트랜잭션

  • read committed 격리수준이라면 , dirty read/write 문제가 해결된다

    • dirty read: 아직 커밋되지 않은 다른 트랜잭션의 데이터를 읽는 것.
    • dirty write:아직 커밋되지 않은 다른 트랜잭션의 데이터를 덮어 쓰는것.
  • read committed 수준이라도 동시성 문제는 사라지지 않는다.

    • A 트랜잭션이 열려있고, 다른 트랜잭션 B 가 완료되었을 때 A 가 트랜잭션이 열린 시점의 데이터가 아니라 B 트랜잭션의 커밋결과를 반영하면 문제가 생긴다.
    • 가령 나의 계좌 2개에서 총 금액을 합하는 트랜잭션이 A였을 때. 계좌 2개가 각각 500원이 있었다. 현실에는 1000원이 있는 셈.
    • 근데 A 트랜잭션이 열리때 하나의 계좌에서 500원을 읽고, B 트랜잭션이 두번째 계좌에서 금액을 첫번째 계좌로 옮겨 400원이 되었다면, A 트랜잭션이 두번째 계좌를 읽으면 400원이 있고 따라서 A 트랜잭션은 500(B 트랜잭션 반영 전 첫번째 계좌) + 400 (B 트랜잭션 반영 후 두번째 계좌) 는 900원이라는 잘못된 금액을 나타낸다.
    • 이런 현상을 nonrepeatable read( read skew ) 라고 한다.
  • nonrepeatable read( read skew ) 를 방지하기 위한 흔한 방법이 스냅샷 격리다.

    • 트랜잭션을 시작할 때 커밋된 상태였던 모든 데이터 상태를 본다. 데이터가 나중에 다른 트랜잭션에 대해 바뀌더라도, 각 트랜잭션은 특정한 시점의 과거 데이터를 볼 뿐이다.
    • 결국 다중 버전 동시성 제어라는 기법을 사용하는데, 데이터베이스가 객체의 여러 버전을 유지하는 것이다.
  • 동시에 트랜잭션이 갱신하면 흔히 손실이 발생한다. lost update

    • 결국 read - modifiy - write 주기에서 읽을 떄와 쓸 때 간극에 여러 트랜잭션이 관여하면 발생한다.
    • 가령 아래와 같은 원자적 쓰기(atomic operation, increment) 코드를 쓰면 동시성 안전(concurrency safe)하다.
    UPDATE A SET c = c + 1 WHERE key = 'foo';
    
    • read modify write 주기가 순차적으로 실행되도록 강제하거나, 대안으로는 갱신손실을 발견하면 abort 하고 재시도하도록 하는 방법이 있다.
    • 데이터 베이스 중에서는 원자적 compare and set 연산을 제공하는 것도 있다.
    UPDATE a SET b = 'foo' WHERE id = 1234 and foo='bar';
    
  • read skew 와 비슷하게 write skew 도 존재한다.

    ```java
    // 트랜잭셔 시작
    
    A) 현재 당직 의사가 2명 이상이라면, B)당직의사를 한명 제거할 수 있다.
    
    // 트랜잭셔 끝
    
    A 조건으 두 트랜잭션이 동시에 통과하고 B 를 진입하는 경우.
    ```
    
  • 격리수준은 이해하기 어렵고, 디비마다 구현에 일관성이 없다. 반복 읽기는 의미는 상당히 다양하다.
  • 디비를 단일 스레드 루프에서 실행가능하게 하여 동시성 문제를 해결하는 모델이 최근에 논의되고 있다.
  • 비관적으로 혹은 낙관적으로 동시성을 제어할지는 성능과 정합성 사이의 트래이드 오프다.

08 분산 시스템의 골칫거리

  • 스킵

09 일관성과 합의

  • 선형성에 대한 정의와, 보장에 대한 인사이트 정리 필요
  • 구체적인 이야기는 피곤, 스킵
Buy Me A Coffee