LabHub
배우기 러닝패스 코스

Data Pipelines

Version the Change and Keep Both Sides Alive

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

판 번호가 붙은 스키마로 JSON Lines 를 다루는 계약 도구 contract.py 를 만든다. 판 사이의 변화를 갈래별로 분류하고, 뒤로 호환과 앞으로 호환을 Avro 의 스키마 해석 규칙대로 판정하고, 여러 판이 섞여 흐르는 전환 기간을 읽기 스키마 하나로 넘긴다.

왜 중요한가

쓰는 쪽과 읽는 쪽은 같은 순간에 바뀌지 않는다. 그 사이에는 반드시 한쪽만 새 코드인 기간이 있고, 그 기간에도 자료는 계속 흐른다. 그래서 스키마 변경을 설계할 때 물어야 할 것은 "이 변경이 맞는가" 가 아니라 "어느 순서로 배포해도 살아남는가" 다. 답은 세 가지로 갈린다. 기본값이 있는 필드를 더하는 것은 양방향으로 안전하고, 기본값 없는 필수 필드를 더하는 것은 새 읽기 코드가 옛 자료를 못 읽게 만들고, 타입 변경은 넓히는 방향과 좁히는 방향이 정확히 반대의 결과를 낸다. 이름 변경은 스키마만 보면 추가 하나와 삭제 하나이고, 별칭으로만 한 사건으로 되돌아온다. 그런데 별칭은 읽는 쪽 스키마의 것만 쓰이므로 이름 변경은 한쪽 방향으로만 산다. 전환 기간을 넘기는 방법은 하나뿐이다. 읽기 스키마에 기본값을 넉넉히 달아 옛 판까지 읽히게 만드는 것이다. 이 실습은 그 기본값 하나가 몇 건을 살리는지를 숫자로 보인다. 채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 스키마와 줄을 차려 놓고 여러분의 도구를 실제로 실행해 분류와 판정을 채점기가 따로 구현한 값과 대조한다. 필드 이름과 타입과 금액은 실행마다 바뀝니다.

단계

  1. /root/evolve/gen_stream.py 를 만들어 실행해 /root/evolve/schemasv1.json 부터 v5.json 까지, /root/evolve/stream 에 판마다 12줄짜리 v1.jsonl 부터 v5.jsonl 까지와 다섯 판이 섞인 mixed.jsonl 을 만드세요.
  2. /root/evolve/contract.pyfields <스키마> 를 만들어 판 번호와 필드 이름, 필수·선택, 기본값을 내게 하세요.
  3. diff <옛 스키마> <새 스키마> 를 더해 추가와 삭제를 분류하게 하세요. 추가는 기본값이 있는 것과 없는 것으로 갈라 냅니다.
  4. diff 가 새 판의 aliases 를 보고 이름 변경을 한 사건으로 묶게 하세요. 묶인 이름은 추가와 삭제 목록에서 빠집니다.
  5. diff 가 타입 변경을 넓히기와 좁히기로 갈라 내게 하세요. 승격 표에 있으면 넓히기, 없으면 좁히기입니다.
  6. compat <옛 스키마> <새 스키마> 를 더해 뒤로 호환과 앞으로 호환을 판정하고 사유를 고정된 코드로 내게 하세요.
  7. read <읽기 스키마> <스키마폴더> <파일> 을 더해 여러 판이 섞인 줄을 한 판으로 읽게 하고, 너그러운 읽기 스키마 /root/evolve/reader.json 을 만들어 두 읽기의 차이를 /root/evolve/window.json 에 적으세요.
  8. 다섯 판의 이력을 /root/evolve/evolve_report.json/root/evolve/evolve_report.md 로 남기세요.

참고

다섯 판을 만들어 내보내기

/root/evolve/gen_stream.py 를 만들어 실행해 /root/evolve/schemasv1.json 부터 v5.json 까지와 /root/evolve/streamv1.jsonl 부터 v5.jsonl 까지, 그리고 다섯 판이 섞인 mixed.jsonl 을 만드세요.

판마다 달라지는 것을 하나씩만 두면 나중에 판정이 무엇을 잡는지 보입니다. v2 는 기본값이 있는 필드를 더하고, v3 는 기본값 없는 필드를 더하고, v4 는 이름을 바꾸면서(별칭을 답니다) 타입을 넓히고, v5 는 그 타입을 다시 좁힙니다. 줄마다 _v 로 어느 판인지 적으세요.

필수와 선택을 기본값으로 가르기

/root/evolve/contract.pyfields <스키마> 를 만들어 version·names·required·optional·defaults 를 JSON 으로 내게 하세요. 기본값이 있는 필드가 선택이고 없는 필드가 필수입니다.

names 는 선언 순서 그대로, requiredoptional 은 정렬해 냅니다. default 키가 있는지 없는지만 보면 되고, 기본값이 빈 문자열이거나 0 이어도 선택입니다 — 값이 아니라 키의 존재로 가릅니다.

더해진 것과 없어진 것 가르기

diff <옛 스키마> <새 스키마> 를 더해 added_with_default·added_required·removed 세 목록을 내게 하세요. 세 목록 모두 정렬해 냅니다.

이름 집합의 차만 구하면 됩니다. 더해진 필드는 새 판의 선언에서 default 키가 있는지로 두 갈래로 나눕니다. 이 단계에서는 별칭과 타입 변경을 아직 보지 않아도 됩니다.

이름 변경을 한 사건으로 묶기

diff 응답에 renamed 를 더하세요. 새 판에만 있는 필드의 aliases 안에 옛 판에만 있는 이름이 들어 있으면 그 둘은 같은 필드입니다. 묶인 이름은 added_*removed 에서 빠집니다.

이름 변경은 스키마만 보면 추가 하나와 삭제 하나입니다. 별칭이 그 둘을 한 사건으로 되돌리는 유일한 장치입니다. 값 표본으로 추측하지 마세요 — 여기서는 우리가 별칭을 적는 쪽이라 추측할 이유가 없습니다.

넓히기와 좁히기 갈라 내기

diff 응답에 widenednarrowed 를 더하세요. 항목은 [새이름, 옛타입, 새타입] 이고, 옛 타입이 새 타입으로 승격되면 넓히기 그 밖은 좁히기입니다. 이름이 바뀌면서 타입도 바뀐 필드까지 봅니다.

승격 표를 딕셔너리 하나로 적어 두면 판정이 한 줄이 됩니다. int 는 long·float·double 로, long 은 float·double 로, float 은 double 로 갑니다. 같은 타입도 승격으로 보아야 뒤에서 호환 판정이 간단해집니다.

뒤로와 앞으로를 따로 판정하기

compat <옛 스키마> <새 스키마> 를 더해 backward·forward·reasons 를 내게 하세요. 사유는 참고 절의 네 가지 코드만 쓰고 정렬해 냅니다.

읽는 쪽이 쓰는 쪽 자료를 읽을 수 있는지 보는 함수 하나를 만들고 인자를 뒤집어 두 방향을 만드세요. 뒤로 호환은 읽는 쪽이 새 판이고, 앞으로 호환은 읽는 쪽이 옛 판입니다. 별칭은 읽는 쪽 스키마의 것만 쓴다는 점이 여기서 결과를 가릅니다.

전환 기간을 읽기 스키마 하나로 넘기기

read <읽기 스키마> <스키마폴더> <파일> 을 더하고, 다섯 판을 모두 읽는 너그러운 읽기 스키마 /root/evolve/reader.json 을 만드세요. schemas/v5.json 로 읽은 결과와 reader.json 으로 읽은 결과를 /root/evolve/window.jsonstrict·tolerant·amount_total 로 적으세요.

엄격한 읽기 스키마는 기본값 없는 필드 때문에 옛 판을 통째로 버립니다. 그 필드에 기본값을 달면 몇 건이 살아나는지가 이 단계의 답입니다. 별칭도 읽는 쪽에 있어야 옛 이름을 따라갑니다. amount_total 은 너그러운 쪽의 값을 적으세요.

판 이력을 한 장으로 남기기

이웃한 판마다 compat 를 돌려 /root/evolve/evolve_report.jsonversions·steps·full·broken 을 적고, /root/evolve/evolve_report.md## 어떤 판이 있나 ## 어느 방향이 깨지나 ## 전환 기간을 어떻게 넘기나 ## 다음 판에 지킬 것 네 절로 쓰세요.

steps{"from": 정수, "to": 정수, "backward": 참거짓, "forward": 참거짓, "reasons": [...]} 목록입니다. full 은 두 방향이 다 되는 짝, broken 은 한 방향이라도 깨지는 짝을 [옛판, 새판] 으로 담습니다. 보고서에는 전환 기간에 살아난 건수를 숫자로 적으세요.