테스트 환경에 회원 데이터를 채우는 작업이었고 특정 판매자 집합의 데이터만 가져온다는 제약이 있었다. cart_items를 가져오려는데 상품의 seller_id가 전부 그 집합 밖이어서 제약을 깨는 것인지가 걸렸다.
Table of contents
Open Table of contents
판단 기준 — 두 축의 구분
여기서 개념을 정리했다. 제약은 회원의 seller_id 축에 걸려 있고 지금 보는 것은 그 상품을 누가 파는지라는 products.seller_id 축이다.
회원은 여러 판매자의 상품을 담으므로 장바구니 상품의 소유자가 그 집합 밖인 것이 오히려 정상이다. 제약을 깨는 것이 아니라 축이 다른 것이었고 전부 집합 안이면 그쪽이 이상하다.
조치 — 행 추가와 계정 유입의 분리
두 가지 방법이 있었는데 그 판매자를 통째로 집합에 넣는 것과 products 행만 추가하고 admins 계정 계열은 따라오지 않게 하는 것이다. 뒤쪽을 골랐다. 이유는 상품 행이 없으면 cart_items가 끊어진 참조가 돼서다. 계정까지 따라오면 그것은 진짜 제약 위반이다. 대신 판매자 없는 상품 행이 테스트 환경에 남는 것을 감수했다.
행 추가와 계정 유입을 나눠서 정하면 참조 무결성과 제약을 둘 다 지킬 수 있었다.
검증 — 원본과의 대조
작업 중에 다른 것이 나왔다. notices의 본문 참조와 wishlists의 product_id 참조에서 LEFT JOIN 결과 대상이 없는 건들이 나왔고 내가 가져오다 빠뜨린 것인가 싶었다.
원본에서 같은 조회를 돌리니 원본에도 같은 건수의 끊어진 참조가 있었다. 내 누락이 아니라 원본 자체의 결함이었다. 이 대조를 안 했으면 없는 실수를 고치려고 재작업했을 것이다. 그래서 끊어진 참조를 발견하면 반드시 원본과 대상 양쪽 건수를 비교해 누락인지 원본 결함인지 먼저 가르기로 했다.
판단 기준 — 도메인이 가른 처리
원본 결함이라고 전부 그대로 두는 것은 아니었다. notices의 본문 참조 끊김은 운영 상태의 복제다. 화면에서 보이는 모습도 운영과 같아야 해서 그대로 뒀다.
wishlists의 삭제된 상품은 회원 액션으로서 의미가 없고 테스트 환경에서 목록을 보면 깨져 보이므로 제거했다. 같은 끊어진 참조인데 그 데이터가 무엇을 뜻하는지에 따라 처리가 갈렸다.
주의 — 다형 참조의 까다로움
wishlists가 다형 참조라 target_id가 target_type에 따라 여러 테이블을 가리켰다. 조인을 한 번에 못 하므로 먼저 GROUP BY target_type으로 분포를 보고 타입마다 다른 조인을 짰다.
다형 참조는 FOREIGN KEY 제약을 못 걸어서 끊어진 참조가 쌓이기 쉽다는 것도 확인됐다. 작업 순서로는 제약이 어느 축인지 확인하고 대상 데이터의 축이 같은지 다른지를 본다. 다르면 무엇만 추가할지 정하고 끊어진 참조를 찾아 원본과 대조한 다음 도메인별 처리를 정한다.
정리
- 제약의 축과 데이터의 축이 다를 수 있고 그것은 위반이 아니다
- 회원 데이터는 여러 판매자에 걸치는 것이 정상이다
- 참조 무결성을 위해 행만 추가하고 관련 계열은 따라오지 않게 한다
- 끊어진 참조를 발견하면 원본과 양쪽 건수를 비교한다
- 가져오기 누락인지 원본 결함인지 먼저 가른다
- 같은 끊어진 참조여도 도메인별로 처리가 다르다
- 다형 참조는 타입별로 나눠 처리한다
FOREIGN KEY를 못 걸므로 끊어진 참조가 쌓이기 쉽다