본문으로 건너뛰기

TCB v104 — 조합 인코딩과 파일 암호화

상태: 구현 완료 — 변환기와 모든 런타임 · 대상 버전 104 (103을 대체, 배포된 적이 없으므로 호환 경로 없음)

v102가 컬럼 블록에 인코딩 9종을 넣었고, v103이 옵셔널 필드의 presence 비트맵을 넣었습니다. v104는 두 가지를 합니다 — 인코딩 4종을 더하되 그중 셋은 새 배치가 아니라 기존 인코딩의 조합이고, v102가 자리만 잡아 두었던 파일 암호화를 실제로 구현합니다.

1. 왜 이것들인가 — 측정이 먼저입니다

이 개정은 「문자열 컬럼을 더 줄이자」에서 출발했지만, 먼저 만든 것은 인코딩이 아니라 계측입니다. 변환기는 이미 적용 가능한 후보를 전부 인코딩해 보고 가장 작은 것을 고르는데, 그 과정에서 계산한 후보별 크기를 버리고 있었습니다. 그것을 보고서로 내게 하고, 형식에 없는 레이아웃 몇 가지를 같은 단위로 재게 한 뒤 두 데이터셋에 돌린 결과가 계획을 바꿨습니다.

성격이 다른 둘입니다 — 한쪽은 행이 많고 옵셔널이 없으며, 다른 쪽은 배열과 옵셔널이 많습니다. 한쪽만 재면 반대 결론이 나옵니다.

무엇이전 소규모 코퍼스이전 대규모 코퍼스
문자열 사전에 남은 여지14.4 KB288 KB
어떤 인코딩도 닿지 않는 컬럼9.7 KB2,171 KB

이전 소규모 코퍼스에서 배열은 바이트의 1.8%였고, v102는 그 숫자를 근거로 「스칼라만 인코딩한다」고 적었습니다. 이전 대규모 코퍼스에서 배열은 바이트의 60.3%입니다. 어느 쪽도 형식의 성질이 아니라 시트의 성질이며, 그래서 선택이 데이터를 따라가야 합니다.

계측 자체는 형식을 바꾸지 않습니다. recipe의 EncodingReport에 경로를 적으면 컬럼마다 원본 크기, 선택된 인코딩, 후보별 실측 크기, 사전·인덱스 분해, 절감률, 그리고 형식에 없는 레이아웃들의 가상 크기를 적습니다. Deflate 수치도 함께 나오는데, 이것은 후보가 아니라 상한입니다 — 어떤 substring 기법도 그 아래로 갈 수 없으므로, 새 레이아웃을 만들기 전에 만들 가치가 있는지를 숫자로 답하게 합니다.

2. 바꾸지 않는 것

바이너리 형식의 불변식은 전부 유지됩니다.

  • 모르는 컬럼 건너뛰기 = advance(byteLength) 한 번. 조합 인코딩의 내부 인코딩 바이트와 길이 스트림도 전부 컬럼 블록 에 있습니다.
  • 어떤 스키마 변경도 감지되지 않는 읽기 오류가 되지 않습니다.
  • 승격 표는 그대로입니다.
  • 행 순서는 파일에 적힌 그대로입니다.
  • 디스크립터 배치도 그대로입니다encoding 바이트 하나가 블록 전체를 결정한다는 성질이 유지됩니다. 조합은 디스크립터가 아니라 블록 헤더에서 일어납니다.

버전 정수는 103 → 104입니다. 형식 버전은 하나뿐이라는 정책도 그대로입니다.

3. 인코딩 — 번호 9~12

번호이름적용 대상
9ARRAY고정 배열·가변 배열 (uuid 원소 제외)
10WHOLEf32·f64 (스칼라, 그리고 ARRAY의 원소)
11DICT_SEGstring
12DICT_SEG_RLEstring

기존 9종의 이름과 번호는 바뀌지 않습니다. DICT_FRONT는 정렬된 사전에서 앞 항목과 공유하지 않는 부분만 적는 기법(front coding)의 표준 명칭이고, DICT_* 접두사는 「사전 + 인덱스 스트림」을, 뒷부분은 「사전 자체를 어떻게 쥐는가」를 뜻합니다. 새 사전 레이아웃이 DICT_SEG로 그 체계에 그대로 들어갑니다.

9 — ARRAY

새 배치가 아니라 조합입니다. 배열 블록은 원소용 인코딩 하나와, 행마다 길이가 다르면 길이용 인코딩 하나를 지목하고, 그 뒤에 각각의 스트림이 옵니다. 리더는 이미 가진 커서 둘로 풉니다.

fixed8 elementEncoding
fixed8 lengthEncoding 가변 배열일 때만
[길이 스트림] 가변 배열일 때만. rowCount개, lengthEncoding대로
[원소 스트림] totalElements개, elementEncoding대로
  • totalElements는 가변 배열이면 길이의 합, 고정 배열이면 rowCount × 디스크립터의 count입니다.
  • 길이는 varint 원소 스트림입니다. 그래서 허용되는 lengthEncoding은 varint 컬럼에 허용되는 것과 같습니다 — RAW(값마다 counter32) 또는 RLE. 행 길이가 전부 같은 컬럼이 대다수이고, 그것은 런 하나가 됩니다.
  • elementEncoding그 원소 타입의 스칼라 컬럼에 허용되는 것과 정확히 같습니다. 문자열 배열은 DICT_SEG까지 닿고, f64 배열은 WHOLE까지 닿습니다.
  • 고정 배열에는 길이 스트림이 없습니다. 개수는 디스크립터에 있고, 행마다 다시 적는 것은 형식이 이미 한 말을 반복하는 것입니다.
  • uuid 원소는 제외합니다. uuid에는 적용되는 인코딩이 없어 커서 경로가 없고, 그래서 얻을 것이 행 길이의 압축뿐입니다. 변환기가 uuid 배열에는 이 후보를 내지 않습니다.

디코드 순서가 곧 배치 순서입니다 — elementEncoding을 읽고, 가변이면 lengthEncoding을 읽고 길이 rowCount개를 전부 푼 다음, 그 자리에서 원소 커서를 만듭니다. 길이를 먼저 다 읽으므로 원소 스트림의 시작 위치는 저절로 맞습니다.

10 — WHOLE

f32·f64 컬럼의 모든 값이 정수일 때, 정수로 싣습니다.

fixed8 innerEncoding VARINT(1)·DELTA(2)·RLE(3)·DELTA_RLE(4) 중 하나
[정수 스트림] count개, innerEncoding대로. 값은 int32

스프레드시트에는 수 종류가 하나뿐이라, 개수·등급·식별자가 부동소수점으로 도착해 값마다 8바이트로 적힙니다. 정수로 적으면 정수 인코딩들이 닿습니다 — 1씩 증가하는 컬럼은 런이 되고, 반복되는 컬럼은 거의 사라집니다.

판정은 「소수부가 없다」가 아닙니다. 정수로 바꿔 다시 쓴 바이트가 RAW 블록이 담았을 바이트와 같아야 합니다. 이 조건이 추가로 걸러내는 것들이 있습니다.

  • 음의 0-0.0+0.0은 비트 패턴이 다릅니다.
  • f32의 정밀도 — 가수부 24비트를 넘는 정수는 f32가 정확히 담지 못합니다.
  • NaN·무한대 — 대응하는 정수가 없습니다.

가정하지 않고 검사하는 이유는 그 실패가 드러나지 않기 때문입니다. 검사가 없으면 컬럼이 「거의 맞는」 값으로 읽히고, 그 아래 어디에서도 그 사실이 드러나지 않습니다.

11 · 12 — DICT_SEG · DICT_SEG_RLE

사전 항목을, 항목들이 공유하는 조각 표에 대한 참조 목록으로 싣습니다.

counter32 segmentCount
segmentCount ×: 바이트 오름차순, front coding
counter32 shared
counter32 restLength
restLength 바이트
counter32 dictCount
dictCount ×:
counter32 pieceCount
pieceCount ×: counter32 segmentIndex
[인덱스 스트림] rowCount개. 11은 평문, 12는 RLE

front coding이 공유할 수 있는 것은 이웃한 두 항목이 앞에서 공유하는 부분뿐입니다. 조각으로 조립된 값 — 경로, 단어로 만든 이름, 구획이 있는 식별자 — 은 가운데와 끝에서도 같은 조각을 반복하는데, front coding은 그 부분을 항목마다 다시 적어야 합니다.

조각은 탐색하지 않습니다. 값의 성격이 바뀌는 자리에서 자릅니다 — 구분자 다음, 그리고 숫자와 글자가 만나는 자리. 결정적이고, 한 번의 순회로 끝나며, 측정한 데이터에서 탐색이 찾았을 것을 찾았습니다.

  • 구분자는 _ - / . \ : (공백) | 이고, 자기가 끝낸 조각에 붙어 있습니다. 이름과 그 뒤의 구분자가 참조 하나가 됩니다.
  • 숫자와 글자 사이의 절단은, 번호만 다른 한 무리의 값들이 번호 말고 전부를 공유하게 합니다.
  • ASCII만 봅니다. 멀티바이트 시퀀스의 바이트는 전부 0x7F를 넘으므로 여기서 구분자도 숫자도 아니고, 각 시퀀스는 자기가 속한 조각 안에 통째로 들어갑니다. 조각이 문자의 절반인 경우는 없습니다.

조각 표를 가지치기하지 않습니다. 벌지 않는 조각으로 가득한 표는 이 후보를 크게 만들고, 큰 후보는 측정에서 front coding에 집니다 — 가지치기가 더 긴 길로 도달했을 답과 같습니다.

front coding을 대체하지 않습니다. 계층적인 값에서는 front coding이 여전히 더 작습니다. 둘 다 후보로 내고 재는 것이 처음부터 이 형식의 선택 방식이었습니다.

조합의 순서

ARRAYWHOLE → 사전. 리더는 이 순서로 블록 헤더를 벗겨 냅니다. WHOLE 안에 ARRAY는 오지 않고, ARRAY의 원소 인코딩으로 WHOLE은 옵니다.

리더의 거부

리더는 (원소, 인코딩) 표에 없는 조합을 만나면 필드 이름과 함께 멈춥니다. v104에서 표는 이렇습니다.

원소허용되는 인코딩
스칼라 아님(배열)RAW · ARRAY
bool · varintRAW · RLE
i32RAW · VARINT · DELTA · RLE · DELTA_RLE
i64RAW · DICT · DICT_RLE
f32 · f64RAW · DICT · DICT_RLE · WHOLE
stringRAW · DICT · DICT_RLE · DICT_FRONT · DICT_FRONT_RLE · DICT_SEG · DICT_SEG_RLE
uuidRAW

디스크립터가 담는 것은 바깥 인코딩뿐이므로, ARRAY내부 인코딩은 디스크립터 검사가 아니라 읽는 자리에서 검사됩니다.

4. 암호화 — 구현

v102 4절이 확정해 둔 envelope을 그대로 구현합니다. 자리를 미리 잡아 둔 덕분에 envelope 때문에 버전을 올리지는 않았습니다(버전이 오른 것은 3절의 인코딩들 때문입니다).

fixed32 version = 104 평문
fixed8 flags bit0 = 1 (암호화)
fixed8 cipher 1 = ChaCha20. 그 외는 거부
bytes12 nonce
── 여기부터 암호문 (keystream XOR, 길이 불변) ──
bytes4 magic = 54 43 42 00 "TCB\0"
counter32 rowCount
counter32 columnCount
…이하 v102 3절과 동일…
  • 암호는 ChaCha20(RFC 8439, 256비트 키, 블록 카운터 0에서 시작)입니다. 모든 언어에 외부 의존성 없이 수십 줄로 들어가고, 하드웨어 가속이 없는 환경에서도 빠르며, 스트림 XOR이라 길이가 변하지 않습니다byteLength 합계 검증을 비롯한 구조 검사가 평문 기준 그대로 성립합니다.

  • nonce는 난수가 아니라 평문의 SHA-256 앞 12바이트입니다. 여기서 「평문」은 magic부터 파일 끝까지, 즉 암호화되는 바이트 전부입니다. 난수 nonce는 같은 입력에서 다른 파일을 만들어 골든 트리와 결정적 출력을 깨뜨립니다. 내용이 같다는 사실이 드러나는 것은 이 위협 모델에서 수용하고, 정작 중요한 성질 — 같은 키에서 내용이 다르면 nonce가 다르므로 keystream 재사용이 없음 — 은 지켜집니다.

  • MAC은 없습니다. 키가 클라이언트에 있는 이상 MAC은 무결성 보장이 아니라 손상 감지인데, 손상 감지는 구조 검증이 이미 합니다. 암호문 헤더의 magic 4바이트가 「키가 다르다」와 「파일이 손상됐다」를 구분해 줍니다.

    이 판단은 MAC과 파일 시그니처에서 뒤집혔습니다. 둘째 문장이 사실과 달랐습니다 — 구조 검증이 검출하는 것은 파일의 구조가 어긋난 경우뿐이고, 고정폭 원소는 어떤 비트 패턴도 유효한 값이므로 f32 4바이트를 바꾸는 변조는 구조를 어긋나게 하지 않습니다. 게다가 keystream XOR은 가단이라 키 없이도 평문의 비트를 뒤집을 수 있습니다 — 즉 이 절의 암호화는 변조 저항을 전혀 제공하지 않았습니다. 지금은 별도 키의 HMAC-SHA-256 태그가 헤더에 있고, 이 항목의 magic은 파일 앞의 시그니처와 암호문 머리의 keyCheck로 나뉘었습니다.

  • layer 순서는 인코딩 → (압축, 예약) → 암호화입니다. 압축은 반복이 있어야 일하므로 암호문 위에서는 무의미하고, 그래서 암호화가 항상 바깥입니다.

리더 쪽 — 제자리 복호

리더는 바이트를 받으면 먼저 envelope을 엽니다. 암호화되지 않은 파일은 그대로 돌아옵니다. 그래서 이 호출은 프로젝트가 키를 쓰든 안 쓰든 로드 경로에 들어갑니다 — envelope이 있을 때만 여는 리더는 두 종류의 파일을 다 받을 수 없는 리더입니다.

복호는 제자리에서 일어나고, 돌아오는 것은 같은 배열에 대한 창(window)이지 사본이 아닙니다. cipher 바이트·nonce·magic은 그 시점에 이미 읽고 검사한 뒤이므로, 그 끝부분에 평문 헤더 5바이트 (version 4 + flags 1)를 덮어쓰고 창을 거기서 시작합니다. 테이블 파일은 클라이언트가 시작할 때 할당하는 가장 큰 것이고, 18바이트를 떼려고 전부 복사하는 비용은 치를 이유가 없습니다.

구현 방침 — 언어마다 다릅니다

직접 구현이 기본이지만, 순수 구현이 느린 언어는 플랫폼이 이미 가진 것을 씁니다. 판단 기준은 「그 언어에서 바이트 단위 루프가 수 MB를 감당하는가」입니다.

언어무엇을 쓰는가
C · C++ · Unreal · C# · Rust · Go · Dart · TypeScript직접 구현. 외부 의존성 없음
Java · KotlinJDK 11+의 javax.crypto ChaCha20표준 라이브러리이므로 의존성이 늘지 않습니다
Pythoncryptography 패키지의 ChaCha20
Ruby표준 라이브러리 opensslOpenSSL::Cipher.new('chacha20')
PHPext-openssl의 openssl_decrypt(..., 'chacha20', ...)

라이브러리를 쓰는 쪽에서 주의할 점 하나. OpenSSL·JCA의 ChaCha20은 16바이트 IV (블록 카운터 4바이트 리틀엔디언 + nonce 12바이트) 또는 (nonce, counter) 쌍을 받습니다. 이 형식은 카운터 0에서 시작하므로 그렇게 넘겨야 합니다.

PHP에서 ext-sodium을 쓰지 않는 이유. 번들되어 있다는 이유로 그쪽이 자연스러워 보이지만, sodium이 내놓는 것은 xchacha20 — nonce가 24바이트인 다른 구성입니다. 이 형식이 쓰는 12바이트 nonce ChaCha20에 대응하는 함수가 없어서, sodium으로 쓴 리더는 변환기가 봉인한 파일을 열 수 없습니다. 처음에 그렇게 적었다가 실제로 돌려 보고 알았고, 그 전까지는 확장이 꺼진 환경이라 「검증되지 않은 경로」로만 보였습니다.

키를 어떻게 다루는가

layer가 무엇을 위한 것인지가 취급 방침을 정합니다. 대칭키는 파일을 읽는 클라이언트 안에 실립니다. 바이너리에서 키를 꺼낼 수 있는 공격자에게 이것은 장벽이 아니고, 그런 공격자를 막는 형식은 존재하지 않습니다. 이 layer가 없애는 것은 두 가지입니다 — 「데이터 파일을 에디터로 열면 다 보인다」와 「값을 바꿔 넣어도 그대로 로드된다」.

그러므로 키를 클라이언트에서 지키려 애쓰는 것은 비용만 드는 일이고, 대신 지켜야 할 것은 키가 저장소·빌드 로그·이슈 트래커에 남지 않는 것입니다. 그것은 실제로 지킬 수 있고, 새는 순간 되돌릴 수 없습니다.

만들기. 키를 손으로 짓지 않습니다. 암호를 해시한 것, 기억하기 좋은 문자열, 짧게 자른 16진수는 전부 키가 아닌 것이 되는 방식입니다.

tabbit --new-encryption-key # 표준 출력으로. 파이프로 시크릿 저장소에
tabbit --new-encryption-key --out my.key # 파일로. 이미 있으면 덮어쓰지 않습니다

키는 표준 출력에 한 줄로 그것만 나가고, 안내문은 표준 오류로 나갑니다. 파이프로 넘겼을 때 키만 넘어가게 하기 위해서입니다. 이미 있는 파일은 건드리지 않습니다 — 조용히 덮어쓰면 그 키로 내보낸 파일 전부가 읽히지 않게 되고, 증상은 「클라이언트가 데이터를 로드하지 못한다」뿐입니다.

변환기(빌드) 쪽. recipe에는 키를 적지 않습니다. recipe는 커밋되고 돌아다니는 파일이며, 거기 적힌 키는 그때부터 저장소의 이력에 남습니다. recipe가 받는 것은 이름과 경로입니다.

"Binary": [
{ "Path": "out/binary", "EncryptionKeyVariable": "TABBIT_TCB_KEY" }
]
// 또는
{ "Path": "out/binary", "EncryptionKeyFile": "/etc/tabbit/tcb.key" }
  • 둘을 함께 적으면 거부합니다. 어느 키로 쓰였는지에 의문이 남지 않게 합니다.
  • 키가 없거나 형식이 틀리면 첫 테이블을 쓰기 전에 멈춥니다. 디렉터리가 절반은 평문, 절반은 암호문으로 남는 상태를 만들지 않습니다.
  • CI에서는 환경 변수 쪽이 보통 맞습니다. 다만 로그에 찍히지 않게 하십시오 — 빌드 스크립트의 set -x, 환경 덤프, 실패 시 진단 출력이 흔한 경로입니다.
  • 키 파일을 쓴다면 저장소 밖에 두고, 저장소 안에 두어야 한다면 .gitignore에 넣기 전에 커밋되지 않았는지 먼저 확인하십시오.

서버 쪽. 서버가 같은 .tcb를 읽는다면 키는 서버의 시크릿 저장소에서 옵니다 — 환경 변수, 키 관리 서비스, 오케스트레이터의 시크릿 중 어느 것이든 그 조직이 이미 쓰는 것을 씁니다. 서버는 클라이언트와 사정이 다릅니다. 키가 프로세스 밖으로 나갈 이유가 없으므로 여기서는 실제로 지킬 수 있고, 그러니 지켜야 합니다.

  • 키를 이미지·컨테이너 레이어·설정 파일에 굽지 않습니다. 실행 시점에 주입합니다.
  • 서버 로그·에러 리포트·크래시 덤프에 키가 들어가지 않게 합니다.
  • 서버가 데이터를 신뢰하는 근거로 이 암호화를 쓰지 않습니다. 서버가 읽는 파일이 진짜인지는 파일을 어디서 받았는지의 문제이지 암호의 문제가 아닙니다. 그 보장이 필요하면 배포 경로에 서명이 있어야 하고, 이 형식은 그것을 하지 않습니다.

클라이언트 쪽. 키가 클라이언트 안에 있다는 것이 전제이므로, 목표는 숨기는 것이 아니라 실수로 넓히지 않는 것입니다.

  • 클라이언트에 넣는 키는 그 용도 전용이어야 합니다. 서버의 다른 비밀, 계정, 배포 자격 증명과 같은 값을 재사용하지 않습니다. 이 키는 언젠가 꺼내질 것이라고 전제하고 고릅니다.
  • 키를 소스에 문자열 리터럴로 두면 저장소에 남습니다. 빌드 시점에 주입하고, 주입 경로가 저장소를 거치지 않게 합니다.
  • 키를 바꾸면 그 키로 내보낸 데이터를 함께 배포해야 합니다. 데이터와 클라이언트가 따로 갱신되는 구조(패처·업데이터)라면, 키 교체는 두 쪽이 같은 시점에 바뀌도록 계획해야 하는 변경입니다.
  • 잘못된 키는 magic에서 그 말로 멈춥니다. 그 메시지를 사용자에게 그대로 보이지 말고, 「데이터를 다시 받으십시오」 정도로 바꾸는 편이 낫습니다.

공통. 키가 샜다고 판단되면 할 일은 하나입니다 — 새 키를 만들고, 데이터를 다시 내보내고, 클라이언트를 다시 배포합니다. 이 형식에는 폐기 목록도 키 회전 절차도 없습니다. 그것이 없는 것이 이 layer의 크기에 대한 정직한 표현입니다.

5. 예상 효과 (측정 기반)

데이터셋v103v104
이전 소규모 코퍼스 — 67개 테이블 · 109,218행165,987 B141,681 B (−14.6%)
이전 대규모 코퍼스의 부분 빌드 7개 테이블4,111,118 B1,460,895 B (−64.5%)
이전 대규모 코퍼스의 부분 빌드 7개 테이블16,032 B4,930 B (−69.2%)

이 표는 커밋된 산출물을 실제로 측정한 것입니다. 개정 중에 인용하던 수치(이전 소규모 코퍼스 158,685 → 134,379)는 계측용으로 타깃을 걷어낸 recipe에서 나온 것이라 테이블 집합이 조금 달랐고, 양쪽이 나란히 7,302 B씩 어긋나 있었습니다. 남는 쪽은 배포되는 파일을 측정한 이 표입니다.

이전 대규모 코퍼스의 인코딩별 사용량:

인코딩컬럼 수바이트
ARRAY39305,171
WHOLE147285,615
DICT_SEG4275,884
DICT_SEG_RLE5226,298
DICT_FRONT5143,241
DICT_RLE30133,026
DICT_FRONT_RLE867,958
RLE3315,694
DELTA_RLE75,719

문자열에서 출발한 작업이 문자열에서 가장 크게 회수하지 않았다는 점이 이 개정의 요지입니다. 이전 소규모 코퍼스의 문자열 사전 여지는 14.4 KB였고, 같은 시점에 어떤 인코딩도 닿지 않는 컬럼이 이전 대규모 코퍼스에서 2.17 MB였습니다. 보고서를 먼저 만든 것이 그 순서를 정했습니다.

6. 검증 게이트

게이트변경
형식 고정버전 바이트가 104로. 나머지 38바이트는 그대로 — 1행짜리 테이블에서는 새 인코딩이 이기지 않습니다
적합성 코퍼스 ×13인코딩 커버리지를 13종으로 확장. ARRAY·WHOLE·DICT_SEG·DICT_SEG_RLE 각각이 실제로 선택되는 컬럼을 코퍼스에 넣습니다
인코딩 선택 단정컬럼별 선택 결과를 갱신
모르는 인코딩 거부새로 추가합니다. v102 스펙이 요구했으나 만들어지지 않은 채로 있던 게이트입니다
암호화 왕복변환기의 Seal과 시프트된 리더의 Open을 한 스위트에 넣고 서로에 대해 단정. RFC 8439의 공표된 시험 벡터를 소스에 박습니다
스큐 코퍼스 ×13그대로. 데이터가 한 세대 새로워도 같은 값이 나와야 합니다
골든 트리재기록. 새 인코딩이 선택되는 컬럼의 diff가 리뷰 대상입니다

7. v104에서 하지 않는 것

  • 범용 압축 레이어. flags bit1이 자리로 남아 있습니다. 계측이 낸 숫자는 무겁습니다 — 이전 대규모 코퍼스에서 인코딩 뒤에도 Deflate가 35.8%까지 더 줄입니다. 다만 그것은 모든 런타임에 압축 해제기를 들이는 값이고, 이번 개정이 같은 파일을 이미 64.5% 줄였으므로 그 판단은 이 뒤에 남는 것을 보고 합니다.
  • 템플릿 + 숫자 추출 사전. 측정하였고, 더 작았습니다 — 이전 소규모 코퍼스에서 11개 컬럼 990바이트, 이전 대규모 코퍼스에서 4개 컬럼 27 KB. 자기 디코드 경로를 모든 언어에 요구하기에는 모자랍니다. 숫자로 된 식별자의 상당 부분은 DICT_SEG의 조각 분할이 이미 처리합니다.
  • 전역 문자열 풀. 이미 실측되어 있습니다 — 문자열 7,568개 중 둘 이상의 컬럼에 등장하는 것은 412개, 이득 1.8%. 그리고 블록 밖의 공유 사전은 「건너뛰기 = advance 한 번」 불변식을 깹니다.
  • 행 재배열. 행 순서는 리더가 리스트를 만드는 순서라 관찰 가능한 의미입니다.
  • 키 회전·서명. 4절 참조. 이 형식이 하지 않는 것을 하는 척하지 않습니다.

8. 구현 순서

  1. 계측 — 인코딩 보고서와 구조 오라클. 완료.
  2. 변환기 — 인코딩 4종, 버전 104, 암호화 envelope, --new-encryption-key. 완료.
  3. C# 런타임과 생성기 — 배열이 커서를 거치게 하고, 새 인코딩 4종과 envelope을 읽습니다. 적합성 코퍼스 왕복 통과. 완료.
  4. 나머지 런타임 — 런타임의 커서·envelope과, 생성기의 「배열도 커서로」 변경이 언어마다 한 벌씩. 완료.
  5. 코퍼스와 게이트 — 6절. 새 인코딩 넷이 각각 선택되는 컬럼과, 모르는 조합을 거부하는 게이트. 완료.
  6. 골든 재기록과 쇼케이스 재생성완료. 기록 없이 재검증하는 것은 전체 스위트가 하는 일이므로 푸쉬 전에 한 번입니다.