4장
애플리케이션은 시간이 지남에 따라 변한다.
대부분의 경우 애플리케이션 기능을 변경하려면 저장하는 데이터도 변경해야 한다.
근데 보통 바로 적용이 안된다(서버/클라쪽에서 바로 적용이 힘들기 때문)
이를 위해 하위호환/상위호환성을 유지해야 한다.
여기서는 JSON, XML, 프로토콜 버퍼 등등.. 데이터 부호화를 위한 다양한 형식을 살펴본다.데이터 부호화 형식
프로그램은 보통 (최소한) 두 가지 형태로 표현된 데이터를 사용해 동작한다.
- 메모리에 객체, 구조체, 리스트, 해시 등등...으로 데이터가 유지되고, 이런 데이터 구조는 CPU에서 효율적으로 접근하고 조작할 수 있게 최적화된다.
- 데이터를 파일에 쓰거나 네트워크를 통해 전송하려면 스스로를 포함한 일련의 바이트열(예를 들어 JSON)형태로 부호화해야한다. 이런 바이트열은 보통 메모리에서 사용하는 데이터 구조화는 다름
요 두 표현 사이의 변환이 필요한데, (인메모리표현 -> 바이트열 : 부호화/직렬화/마샬링) (고 반대 -> 복호화/파싱/역직렬화/언마샬링) 이라고 한다.
- 참고로 여기 직렬화는 트랜잭션 그거랑은 다름 - 헷갈릴까봐 여기서는 이제 부호화라고 한다.
언어별 형식
언어마다 자체적으로 인메모리 객체를 바이트열로 부호화하는 기능을 내장한다. (java의 serializable등등)
- 근데 이거는 문제가 있는데
- 보통 특정 프로그래밍 언어랑 묶여있어서 다른 언어에서 데이터 읽기가 힘들다. 즉 이렇게 저장하면 현재 프로그래밍 언어로만 개발해야하고 다른 시스템과 통합에 문제가 있다.
- 동일한 객체 유형 복원에는 복호화 과정이 임의의 클래스를 인스턴스화할 수 있어야한다. => 이게 근데 왜 문제가 되는지는 모르겠다..
- 내가 생각할 때에는 사용자가 임의로 값을 삽입해서 보낼 수 있어서일것 같은데 맞을까? 확인필요
- 버전 관리의 문제
- 효율성도 문제
JSON, XML, 이진 변형
- 근데 이거는 문제가 있는데
수 부호화
- XML, CSV는 수와 숫자로 구성된 문자열을 구분할 수 없다.
- JSON은 문자열과 수를 구분하지만 정수와 부동소수점 수를 구별하지 않고 정밀도를 지정하지 않는다.
- 자바도 부동소수점 쓰기는 하는데, flaot의 문제랑 관련된 그거같은 느낌은 아니다.
- 일단 자바스크립트 관련 내용을 살펴보았는데
유니코드 문자열 지원(사람이 읽을 수 있는)
- JSON, XML은 잘 지원한다.
- 근데 이진 문자열(문자 부호화가 없는 바이트열) 을 잘 지원하지 않는다.
- 이게 유용해서 Base64를 쓰기는 한데 정공법이랑 다르고, 데이터 크기 증가
- 이거 생각해보니 API보낼때 논의되었던 내용중 하나다. 확실히 용도가 좋음
- But 좀 애매한게 있긴했다.
- 이거 생각해보니 API보낼때 논의되었던 내용중 하나다. 확실히 용도가 좋음
- 이게 유용해서 Base64를 쓰기는 한데 정공법이랑 다르고, 데이터 크기 증가
- 근데 이진 문자열(문자 부호화가 없는 바이트열) 을 잘 지원하지 않는다.
- JSON, XML은 잘 지원한다.
스키마 지원
- JSON, XML 모두 스키마 지원
- CSV는 스키마가 없어서 각 로우와 컬럼 의미 정의는 애플리케이션에서 해줘야 한다.
이진 부호화
JSON이랑 XML은 둘다 공간을 많이 차지함.
- 그나마 JSON이 XML보다는 덜 장황하지만 그래도 이진 형식보다 훨씬 많은 공간 차지
이진 부호화는 스키마 지정을 안하기 때문에 데이터 안에 모든 객체의 필드 이름을 포함해야 한다.
스리프트와 프로토콜 버퍼
아파치 스리프트와 프로토콜 버퍼는 같은 원리를 기반으로 한 이진 부호화 라이브러리.
스키마 기술이 필요하다. (IDL : interface definition language)
바이너리프로토콜
- 타입 주석(이게 문자열인지 정수인지 등등)
- 필드 태그(필드 이름을 JSON처럼 쓰지 않고 이렇게 하면 나중에 확인가능)
- 부호화된 데이터
스리프트 컴팩트프로토콜
- 바이너리프로토콜하고 비슷한데, 필드 타입이랑 태그 숫자를 단일 바이트로 줄이고 가변 길이 정수를 사용해서 부호화
필드 태그와 스키마 발전
스키마는 필연적으로 시간이 지남에 따라 변한다. 이를 스키마 발전이라고 부름.
그래서 이 하위/상위 호환성을 어떻게 유지할까?부호화된 레코드는 사실상 부호화된 필드의 연결일 뿐이다.
- 필드 태그를 통해 새로운걸 추가하는건 걍 새로운 번호를 쓰면 거기서 보니까 괜찮다.
- 상위 호환 : 이러면 예전 코드에서 새로운 코드로 기록한 데이터는 읽지 못하니까 그냥 무시한다.
- 하위 호환 : 고유 태그 번호가 있다면 그냥 같은 의미이기 떄문에 읽기는 쉽게 가능하다.
- 다만 새로운 필드 추가의 경우
required
추가는 안된다.- 하위 호환성 유지를 위해서는 배포 후 추가되는 필드를
optional
혹은 기본값을 주어야 한다.
- 하위 호환성 유지를 위해서는 배포 후 추가되는 필드를
- 다만 새로운 필드 추가의 경우
- 필드 삭제 : 이거는 optional필드만 삭제가능하고 같은 태그 번호는 절대로 다시 사용 불가능하다.
데이터타입과 스키마 발전
- 필드 태그를 통해 새로운걸 추가하는건 걍 새로운 번호를 쓰면 거기서 보니까 괜찮다.
필드의 데이터타입 변경
- 값이 정확하지 않거나 짤릴수도 있다.
repeated
- 이거는
required
,optional
처럼 필드 표시 태그이다. - 이전 데이터를 보는 새로운 코드 : 필드 존재 여부에 따라 0이나 1개의 엘리먼트가 있는 목록으로 봄
- 새로운 데이터를 읽는 예전 코드 : 목록의 마지막 엘리먼트만 봄
아브로
프로토콜 버퍼, 스리프트같은 부호화 형식 중 하나이다.
하둡의 하위 프로젝트로 시작됨(스리프트가 하둡의 사용 사례에는 적합하지 않아서)- 이거는
부호화할 데이터 구조를 지정하기 위해 스키마 사용
얘는 두개의 스키마 언어가 있는데
- 사람이 편집할 수 있는 아브로IDL
- 기계가 더 쉽게 읽을 수 있는 JSON기반 언어
이거는 스키마에 태그 번호가 없다.
- 그래서 이진 부호화 길이가 32바이트로 모든 부호화 중 길이가 가장 짧다.
부호화된 바이트열을 보면 필드나 데이터타입 식별을 위한 정보가 없다.
- 즉 단순히 연결된 값으로 구성된다.
아브로를 이용해 이진 데이터 파싱하려면 스키마에 나타난 순서대로 필드를 살펴보고 스키마를 이용해 각 필드의 데이터타입을 미리 파악해야한다.
- 그니까.. 읽기/쓰기가 정확히 같은 스키마를 사용해야 한다는 것이다.
쓰기/읽기 스키마
위의 내용에서 스키마가 정확히 같아야 한다고 했는데, 스키마 발전을 하려면 어떻게 해야할까?
애플리케이션이 파일, DB에 쓰기 위해 혹은 네트워크 전송을 위해 데이터를 아브로 부호화하려면 알고 있는 스키마 버전을 사용해 데이터를 부호화한다. -> 쓰기 스키마
반대로 수신한 데이터를 복호화하길 원한다면 특정 스키마로 복호화되기를 기대한다. -> 읽기 스키마
그래서 이거의 핵심 아이디어는 둘이 꼭 동일하지 않아도 호환 가능하기만 하면 된다는것.
- 그래서 이거는 데이터 복호화(읽기)때에 쓰기 스키마랑 읽기 스키마 모두를 살펴본 다음 쓰기 스키마에서 읽기 스키마로 변환해 그 차이를 해소한다.
스키마 발전 규칙
아브로에서
- 상위 호환성
- 새로운 버전의 쓰기 스키마와 예전 버전의 읽기 스키마를 가질 수 있음.
- 하위 호환성
- 새로운 버전의 읽기 스키마와 예전 버전의 쓰기 스키마를 가질 수 있음.
- 상위 호환성
호환성 유지를 위해서는 기본값이 있는 필드만 추가하거나 삭제할 수 있다.
- 예를들어 예를들어 기본값만 있는 필드가 새로운 스키마에는 있고 예전 스키마에는 없는 경우
- 예전 스키마로 기록된걸 새로운 스키마로 읽으면 누락된 필드가 기본값으로 채워짐.
- 예를들어 예를들어 기본값만 있는 필드가 새로운 스키마에는 있고 예전 스키마에는 없는 경우
아브로는 널을 허용하려면 유니온타입(Union type)을 사용해야 한다.
얘는 그래서
optional
,required
표시자가 따로 없다.쓰기 스키마는 그래서 뭐지
그래서 읽기는 특정 데이터를 부호화한 쓰기 스키마를 어떻게 알 수 있을까?
상황마다 다름
- 많은 레코드가 있는 대용량 파일
- 파일에 시작 부분에 한번 소개함
- 개별적으로 기록된 레코드를 가진 데이터베이스
- 모든 부호화된 레코드의 시작 부분에 버전 번호를 포함하고, DB에서는 스키마 버전 목록 유지
- 읽기의 경우 레코드에서 버전 번호를 추출한 다음 DB에서 해당하는 쓰기 스키마를 가져옴.
- 네트워크 연결을 통해 레코드 보내기
- 두 프로세스가 양방향 네트워크 연결로 통신할 떄 연결 설정에서 스키마 버전 합의 가능
동적 생성 스키마
- 많은 레코드가 있는 대용량 파일
아브로는 위에서 봤듯이 태그 번호가 포함되어 있지 않다.
이게 동적 생성에 친숙하다는 장점이 있다.
- 관계형 스키마로부터 아브로 스키마를 쉽게 생성 가능하다. (JSON)
- 그리고 이 스키마를 통해 DB내용 부호화하고 아브로 객체 컨테이너 파일로 덤프 가능하다.
- DB테이블에 맞게 레코드 스키마 생성하고 각 컬럼은 해당 레코드의 필드가 된다.
- 그래서 DB스키마가 변경되면 갱신된 DB스키마로부터 새로운 아브로 스키마를 생성하고 새로운 아브로 스키마로 데이터를 내보낸다.
코드 생성과 동적 타입 언어
- 관계형 스키마로부터 아브로 스키마를 쉽게 생성 가능하다. (JSON)
스리프트와 프로토콜 버퍼는 코드 생성에 의존함.
- 스키마 정의 후 선택한 프로그래밍 언어로 구현 코드 생성가능
- 복호화된 데이터를 위해 효율적인 인메모리 구조를 사용하고 데이터 구조에 접근하는 프로그램을 작성할 떄 IDE에서 자동완성 가능.
js, ruby, python같은 동적 타입 프로그래밍 언어에서는 만족시킬 컴파일 시점의 타입검사기가 없어서 코드 생성이 중요하지 않음.
- 아브로는 정적 타입을 위해 코드 기술을 제공하기는 하는데, 코드 생성 없이도 사용가능하다.
스키마의 장점
프로토콜 버퍼, 스리프트, 아브로 모두 스키마를 사용해 이진 부호화 형식을 기술함.
XML, JSON보다 훨씬 간단하고 더 자세한 유효성 검사 규칙을 지원함.
JSON, XML, CSV같은 텍스트 데이터 타입이 널리 사용되지만 스키마를 기반으로 한 이진 부호화 역시 가능.
- 필드 이름을 생략 가능해서 이진 JSON변형보다 크기가 작을 수 있다.
- 스키마는 유용한 문서화 형식인데 복호화를 할 때 스키마가 필요하기 때문에 스키마가 최신 상태인지 확인 가능.
- 이걸 유지하면 스키마 변경이 적용되기 전에 상위 호환성과 하위 호환성 확인 가능.
- 정적 타입 프로그래밍 언어 사용자에게 스키마로부터 코드를 생성하는 기능은 유용하다. 컴파일 시점에 타입 체크가 가능해서이다.
데이터플로 모드
매우 추상적인 개념으로, 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 방법은 아주 많다.
누가 데이터를 부호화하고, 누가 그걸 복호화할지?
- 데이터베이스를 통해
- 일단 여기서 하위 호환성은 반드시 필요하다. 이전에 기록한 내용을 미래의 자신이 복호화해야 하기 때문이다.
- 그리고 상위 호환성도 대개 필요한데, 그 이유는 여러 프로세스가 DB에 접근할 것이고 새로운 코드가 기록되어도 예전 코드를 읽을 수도 있기 때문이다.
- 참고로 중요한것은 예전 코드가 해석이 안돼도 새로운 필드를 유지해야한다는것.
- 복호화 이후 재부호화 과정에서 손실되면 안되므로.
- DB는 주로 애플리케이션같은것에 비해 오래 데이터가 유지된다.
- 바뀌는일은 적지만 마이그레이션은 해야할 수도 있다.
- 보통은 null을 기본값으로 갖는 새로운 컬럼 추가 스키마 변경이 허용됨.
- 예전 로우를 읽는 경우 디스크 상의 부호화된 데이터에서 누락된 임의 컬럼은 널값으로 채움.
- 보관 저장소
- DB스냅숏 관련
- 보통 데이터 덤프는 최신 스키마를 사용해 부호화한다.
- 다양한 시점의 스키마 버전이 섞여 포함되었다고 해도 복사본을 일관되게 부호화하게.
- 서비스를 통해 : REST, RPC
- 서버-클라이언트
- REST, SOAP등이 있고 얘들은 JSON, RPC는 부호화를 쓴다.
- RPC모델은 원격 네트워크 서비스 요청을 같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메서드를 호출하는 것과 동일하게 사용 가능하게 해준다.(위치 투명성)
- RPC는 참고로 결함이 있는데 타임아웃이나 멱등성 등등...
- RPC 데이터 부호화 발전에 대해
- 일단 모든 서버를 먼저 갱신하고 나서 모든 클라이언트를 갱신해도 문제가 없다고 가정한다. -> 즉 요청은 하위 호환성만 필요하고 응답은 상위 호환성만 필요
- 참고로 이게 다른 조직의 것들을 사용할 수 있기 때문에 호환성은 오랜 시간동안 유지돼야 한다.
- 호환성을 깨는 변경이 필요하면 서비스 제공자는 여러 버전의 서비스 API를 함께 유지한다.
- 메시지 전달을 통해
- 위의 두가지를 정리하면
- DB : 일단 부호화된걸 저장하고 나중에 읽을때 복호화
- 서비스 : 하나의 프로세스가 네트워크를 통해 다른 프로세스로 요청을 전송하고 가능한 빠른 응답을 기대
- 이거는 RPC와 DB간 비동기 메시지 전달 시스템
- 먼저 클라이언트 요청을 빠르게 다른 프로세스로 전달(요거는 RPC랑 비슷함)
- 그 요청(메시지)를 직접 네트워크 연결로 전송하지 않고 메시지 브로커나 메시지 지향 미들웨어를 통해 전달함(이런거는 DB랑 비슷)
- 참고로 이거는 단방향이다.
- 즉 비동기로, 송신 프로세스는 그냥 메시지 전달 후 잊어버림.
- 여기의 메세지 브로커(rabbitMQ, kafka등)은 특정 데이터 모델을 강요하지 않고 모든 부호화 형식 사용가능.
- 부호화가 상하위 호환성을 모두 가지면 브로커에서 게시자와 소비자를 독립적으로 변경해서 임의 순서로 배포 가능.
- 액터 모델은 단일 프로세스 안에서 동시성을 위한 프로그래밍 모델이다.
- 스레드를 직접 처리하는 대신 로직이 액터에 캡슐화된다.
- 액터는 보통 하나의 클라이언트나 엔티티를 나타냄.
- 스레드를 직접 처리하는 대신 로직이 액터에 캡슐화된다.
- 위의 두가지를 정리하면
정리
- 데이터베이스를 통해
많은 서비스가 새로운 버전의 서비스를 동시에 모든 노드에 배포하는 방식보다 한 번에 일부 노드에만 서서히 배포하는 순회식 업그레이드가 필요하다.
- 애플리케이션 변경을 쉽게 할 수 있는 발전성에 도움이 많이 된다.
시스템을 흐르는 모든 데이터는 하위호환성(새로운 코드가 예전 데이터를 읽을 수 있음)과 상위호환성(예전 코드가 새로운 데이터를 읽을 수 있음)을 제공하도록 부호화해야 한다.
- 언어별 부호화는 단일 프로그래밍 언어로 제한되며 상위호환/하위호환을 제공하지 못하는 경우가 있음.
- JSON, XML, CSV같은거는 널리 사용됨. 데이터타입에 다소 모호한 점이 있기 때문에 숫자나 이진 문자열과 같은 항목은 주의해야한다.
- 스리프트, 프로토콜 버퍼, 아브로같은 이진 스키마 기반 형식은 짧은 길이로 부호화된다.
- 정적 타입 언어에서 문서와 코드 생성에 유용하지만 사람이 읽기 위해서는 복호화해야 하는 단점이 있음.
그럼 어떻게 부호화할지에 대한 데이터플로우는?
- DB에 기록하는 프로세스가 부호화하고 DB읽는 프로세스가 복호화하기
- 클라이언트가 요청을 부호화하고 서버는 요청복호화/응답부호화, 다시 클라이언트는 응답 복호화하는 RPC, REST API
- 송신자가 부호화하고 수신자가 복호화하는 메시지를 서로 전송하는 비동기메시지 전달
'이론 정리' 카테고리의 다른 글
CAP (0) | 2024.12.05 |
---|---|
데이터 중심 애플리케이션 설계 5장 정리 (2) | 2024.11.22 |
데이터 중심 애플리케이션 설계 2장 정리 (0) | 2024.11.18 |
확장성과 성능에 관한 간단한 정리 (3) | 2024.11.10 |
RAG 이론 정리와 활용 (0) | 2024.08.11 |