orders 테이블의 컬럼 하나를 없애려다 다른 팀 배치가 멈췄다.
Table of contents
Open Table of contents
안 쓰는 컬럼인 줄 알았다
지우려던 컬럼은 이것이었다.
`legacy_order_type` char(2) DEFAULT NULL
우리 코드에서 이 컬럼을 읽는 자리가 없었다.
$ grep -rn "legacy_order_type" --include=*.php src/
(없음)
grep 이 아무것도 안 냈으니 안 쓰는 것으로 보고 없앴다. 다음 날 다른 팀에서 연락이 왔다.
우리 저장소를 훑어 안 나왔다는 판단이 grep 이 닿는 범위 안에서만 유효했다. 그 범위 밖에서 그 컬럼은 계속 쓰이고 있었다.
grep 은 우리 src/ 아래만 본다. 다른 팀의 저장소는 우리 디스크에 없으므로 볼 방법이 없다.
grep 이 아무것도 안 냈다는 것은 우리 .php 에 없다는 뜻뿐이었다. 그것을 안 쓴다는 뜻으로 읽은 것이 실수였다.
원인 — 밖에서 직접 읽는 구조
settle_batch 계정으로 도는 정산 배치가 우리 테이블을 직접 조회하고 있었다.
SELECT order_no, legacy_order_type, amount FROM orders WHERE ...
우리 코드에는 없고 그쪽 코드에 있으니 우리 저장소만 뒤져서는 절대 못 찾는다. 남이 읽고 있으면 그 컬럼은 우리 것이 아니라 약속이었다.
우리가 만든 테이블이고 우리가 채우는 값인데도 그렇다. 읽는 쪽이 밖에 있으면 그 순간부터 그 컬럼은 공개된 규격이 된다.
소유와 약속이 다른 것이었는데 orders 는 우리 것이지만 모양을 바꿀 자유는 없었다.
누가 SELECT 를 시작한 시점부터 그렇게 되는데 그 시점에 우리에게는 아무 알림도 안 온다.
누가 읽고 있는지 찾았다
같은 일이 다른 컬럼에서도 날 수 있으니 누가 붙는지부터 확인했다.
SELECT user, host, db, COUNT(*) FROM information_schema.processlist
WHERE db = 'order' GROUP BY user, host;
information_schema.processlist 로 접속 계정을 보니 우리 서비스 말고 넷이 더 있었다.
settle_batch batch-01
bi_etl etl-01
cs_admin admin-02
report report-01
넷 다 orders 를 직접 읽고 있었는데 계정만으로는 무엇을 읽는지를 알 수 없다.
그 계정이 그 순간 돌리는 쿼리만 보이기 때문이다. 그래서 각 팀에 물어 컬럼 목록을 받았고 우리가 몰랐던 사용처가 몇 개 더 나왔다.
변경 내용 — 무엇이 약속인지 적기
받은 목록을 한 자리에 적었다.
orders 테이블 외부 참조
settle_batch order_no, amount, pay_date, legacy_order_type
bi_etl 전체 (매일 새벽 덤프)
cs_admin order_no, member_no, state, reg_date
report order_no, amount, channel
이 컬럼들은 바꾸기 전에 해당 팀에 알린다.
목록이 있으니 무엇을 마음대로 바꿔도 되는지가 갈렸다. 목록에 없는 컬럼은 우리가 정하고 있는 것은 알리고 바꾼다.
목록이 없을 때는 orders 의 모든 컬럼이 똑같이 위험해 보였고 그러면 아무것도 못 바꾸거나 확인 없이 바꾸게 된다.
스물 몇 개 중 열두 개만 약속이었고 나머지는 그전처럼 우리가 정하면 되는 것이었다.
적어 두지 않으면 급할 때 확인하는 단계가 통째로 빠진다. 알리는 것을 절차로 만들어야 그 단계가 남는다.
직접 읽는 것을 줄이려 했다
근본은 다른 팀이 우리 테이블을 직접 읽는 구조였다.
지금 그쪽 배치 → 우리 DB
바꾸면 그쪽 배치 → 우리 API → 우리 DB
API 로 바꾸면 안쪽 구조를 우리가 바꿀 수 있다. 그런데 그쪽 배치는 대량으로 읽는 것이라 API 가 느렸다.
전부 바꾸지는 못했고 대신 읽기 전용 사본을 만들어 그쪽이 그것을 보게 했다.
운영 DB → 사본 DB → 다른 팀 배치
운영에 부하를 안 주면서 사본의 구조를 우리가 맞춰 줄 수 있었다. 자주 쓰이는 것부터 VIEW 를 만들어 그 모양만 유지하는 방식도 함께 썼다.
사본을 두면 운영 쪽 orders 는 다시 우리 것이 되고 바깥에 보이는 모양은 사본과 VIEW 가 맡는다.
전부를 API 로 옮기지 못한 것은 아쉬웠는데 대량으로 읽는 배치에 강요하면 그쪽 시간이 몇 배가 된다.
전체 흐름 — 바꿀 때 알리는 순서
목록이 생긴 뒤로 컬럼을 손댈 때의 순서를 정했다.
1. 바꿀 컬럼이 외부 참조 목록에 있는지 본다
2. 있으면 해당 팀에 2주 전에 알린다
3. 컬럼을 바로 지우지 않고 먼저 안 쓰게 한다
4. 4주 뒤에 지운다
셋째가 중요했는데 값을 NULL 로 두고 컬럼은 남기면 읽는 쪽이 그것을 보고 알아챈다.
지우면 Unknown column 으로 쿼리가 실패해 배치가 멈추므로 NULL 쪽이 나았다.
검증 — 사본에서 먼저 확인
바꾸기 전에 사본에 먼저 반영해 그쪽이 확인하게 했다.
1주차 사본에 변경 반영
2주차 그쪽에서 확인
3주차 운영 반영
한 번은 이 단계에서 걸렸다. 컬럼 타입을 바꾸려 했는데 그쪽 코드가 그 값을 문자열로 다루고 있었다.
운영에 바로 반영했으면 그쪽 배치가 조용히 틀린 값을 넣었을 것이다. 사본에서 한 주를 먼저 돌린 것이 그 사고를 막았다.
정리
- 우리 코드에 없다고 안 쓰는 것이 아니다
grep의 판단은 우리 저장소 안에서만 유효하다- 남이 읽고 있으면 그 컬럼은 우리 것이 아니라 공개된 규격이다
- 접속 계정은 조회로 보고 무엇을 읽는지는 물어서 목록을 만든다
- 목록에 없는 것은 우리가 정하고 있는 것은 알리고 바꾼다
- 알리는 것을 절차로 만들지 않으면 급할 때 빠진다
- 직접 읽기를 줄이려면
API나 읽기 전용 사본이나VIEW를 둔다 - 지우기 전에 값을 안 채우는 단계를 둔다
- 빈 값이 쿼리 실패보다 낫고 배치가 안 멈춘 채로 알아챈다
- 사본에 먼저 반영해 그쪽이 확인할 시간을 준다