글 목록

시설의 뿌리를 찾아서

같은 사업장이 배출처와 처리장과 운송사와 국가 시스템에 제각각 등록되는 물류 플랫폼에서 시설의 뿌리를 국가 사업장 코드에 두었다. 뿌리를 모르는 시설엔 임시 계보를 주고 알게 되는 순간 하나로 합치는 설계와 그 대가를 적었다.

주재범7분 읽기

한 사업장이 우리 시스템 안에서 몇 번 태어나는지 세어 봤다. 배출처가 자기 시설로 한 번, 처리장이 거래처로 한 번, 운송사가 상하차지로 한 번. 여기에 국가 폐기물 관리 시스템인 올바로 안에서도 배출·운반·처리 인터페이스마다 한 번씩 등장한다. 같은 땅 위의 같은 공장이 대여섯 개의 행으로 흩어져 있었고 그 행들이 같은 곳이라는 사실을 아는 것은 사람뿐이었다. 소유나 소속 같은 판단이 여러 축에 흩어져 있으면 코드는 매번 그 축들을 조합해 답을 만든다. 조합 방식이 경로마다 조금씩 다르면 같은 질문에 다른 답이 나온다. 데이터베이스는 다수결을 모른다. 시설의 뿌리를 어디에 두고 흩어진 사본들을 어떻게 한 줄기로 모았는지 정리했다.

시설의 뿌리는 국가 시스템이 사업장마다 부여하는 사업장 코드에 두었다. 뿌리를 아직 모르는 시설에는 소유 그룹을 축으로 임시 계보를 발급하고 뿌리를 알게 되는 순간 승격해 한 계보로 합친다. 소유 판단은 "계보가 그룹과 이어지는 장소가 그 그룹의 시설이다"라는 판별식 하나로 수렴시켰다. 정체성의 정본은 그룹이고 나머지는 승계만 한다.

뿌리는 밖에 있었다

폐기물이 한 번 움직이면 내놓는 회사, 나르는 회사, 받아서 처리하는 회사가 한 거래에 함께 등장한다. 그 사이의 주문과 계약, 정산을 받아내는 플랫폼을 만들고 있는데, 그 모두의 발밑에 있는 '시설'은 유난히 까다로운 개념이다. 이유가 넷 있다.

  • 정체성의 권위가 밖에 있다. 폐기물 사업장은 국가 시스템이 사업장마다 전국 유일 코드를 부여한다. 이 사업장 코드(entn)가 그 사업장의 공식 신분이다. 사업자번호로는 대신할 수 없다. 한 건설사의 현장 열 곳이 같은 사업자번호를 쓰면서 각자 다른 사업장 코드를 갖기 때문이다.
  • 역할은 사업장의 속성이 아니다. 국가 시스템의 업체 구분 코드는 역할 조합을 담는다. 배출자와 운반자와 처리자 단독뿐 아니라 셋을 다 하는 사업장도 코드 하나로 표현된다. 그러니 어떤 시설이 배출처인지 처리장인지는 그 거래에서 맡은 역할로 정해진다.
  • 국가 데이터 안에서도 흩어져 있다. 같은 사업장이 배출 조회와 처리 조회와 운반 조회 인터페이스, 그리고 대장마다 따로 등장한다. 우리는 이것을 사업장 코드 기준 한 행의 사업장 마스터로 정규화해 모든 테넌트가 공유하게 했다. 온보딩과 인계서 매칭은 흩어진 원본 대신 이 마스터를 본다.
  • 우리 테넌트는 뿌리를 모른 채 시설을 등록한다. 국가 시스템과 연동하지 않은 회사도 가입하고 수기로 시설을 등록한다. 거래 상대의 시설을 자기 관점의 사본으로 만들기도 한다. 뿌리를 모르는 상태가 기본 상태다.

그래서 시설 데이터는 서로 다른 세 질문에 답해야 한다.

  • 정체성: 흩어져 등록된 이 장소들이 실은 같은 사업장인가?
  • 소유: 이 시설은 어느 조직의 것인가?
  • 자리: 이 장소는 싣는 곳으로 쓰는가, 받는 곳으로 쓰는가?

세 질문의 답은 각각 다른 곳에 흩어져 있었다. 정체성은 장소마다 무작위로 발급되는 계보 코드에, 소유는 시설과 소유 조직을 잇는 매핑 행의 존재와 그 안의 소유 키에, 자리는 장소의 유형 필드에 담겨 있었다. 코드 곳곳이 이 셋을 제각각의 방식으로 조합해 "누구의 시설인가"를 판단했다. 그리고 국가 시스템의 사업장 코드는 이 판단 어디에도 끼지 못하고 참고 컬럼으로만 놓여 있었다.

갈라진 뿌리들

  • 같은 회사가 서로 남남이 됐다. 계보 코드를 발급하는 경로가 여러 곳이었다. 가입 경로와 거래 상대의 시설 사본을 만드는 경로, 심지어 저장 시점의 자동 발급 훅까지 제각각 계보를 만들 수 있었다. 그래서 같은 회사의 그룹과 대표 사업장이 서로 다른 계보를 들고 갈라지는 일이 생겼다. 그 회사가 뒤늦게 국가 시스템과 연동해 사업장 코드를 얻으면 임시 계보를 공식 계보로 승격하는 일괄 처리가 돈다. 그런데 그룹 쪽만 갱신됐다. 장소는 옛 계보를 든 고아로 남았다. 발급 경로가 여럿인 식별자는 결국 갈라진다.
  • 같은 계약이 다섯 갈래로 쌓였다. 해지나 만료로 이력만 남은 상대와 다시 계약하면 매번 새 계보가 시작됐다. 개발 데이터에서 한 상대와의 같은 계약이 다섯 계보에 100건 넘게 남남처럼 쌓인 사례가 나왔다. 갱신인지 신규인지 코드가 구분할 근거 자체가 없었다. 문제는 쌓인 데이터보다 물어볼 방법이 없던 코드 쪽이었다. 데이터는 죄가 없다.
  • 소속 조사가 3중 조인이었다. "이 계보는 어느 그룹 것인가"에 답하려면 장소와 매핑, 그룹 세 테이블을 이어 붙여야 했다. 조인 세 번이면 되지 않나 싶지만, 말이 쉽지. 고아 계보를 복구하는 마이그레이션조차 그 조인을 돌려야 했다.

지키면서 바꿔야 했던 것들

  • 운영 중인 시설 데이터 수백 건을 멈추지 않고 점진적으로 전환해야 한다.
  • 외부에 노출된 API 경로와 응답 모양은 그대로여야 한다.
  • 이미 발급된 계보 값은 재발급 없이 새 값과 공존해야 한다.
  • 국가 시스템에 없는 시설도 1급으로 다룬다. 사업장 코드가 없다는 이유로 등록을 막을 수 없다.
  • 사업자번호는 정체성 키로 쓸 수 없다. 한 사업자번호 아래 사업장이 여럿이다.

세 갈래 길

1. 기존 필드에 정합성 검증을 강화

  • 장점: 변경이 가장 적다.
  • 단점: 판단 기준이 여전히 여러 축에 흩어져 있어 새 경로가 추가될 때마다 같은 사고가 재발한다.

2. 계보 동일성 판별식으로 수렴. 계보의 뿌리는 사업장 코드, 없으면 임시 계보

  • 장점: "누구의 시설인가"가 조건식 하나로 끝난다. 매핑 계층을 걷어낼 수 있다. 국가 시스템의 신분과 우리 시설이 같은 축에 선다.
  • 단점: 예외 없는 각인 규율이 필요하고 승격 스윕과 전환 순서 관리가 든다.

3. 역할 기반 동적 해석

  • 장점: 유연하다.
  • 단점: 판단이 호출 맥락에 따라 달라져 예측 불가능한 경계 케이스가 늘어난다.

뿌리는 하나, 판별식도 하나

2를 택했다. 1은 사고가 날 때마다 검증 한 줄을 더하는 길이라 일찍 접었다. 3은 유연하다는 말이 곧 예측 불가라는 말이어서 뺐다. 실행 규칙은 네 가지였다.

계보의 뿌리는 사업장 코드다. 모르면 임시 계보를 주되 형식으로 구분한다. 사업장 코드를 알면 계보 값은 그 코드 그대로다. 그래서 두 테넌트가 서로를 모르고 같은 사업장을 등록해도 같은 계보에 저절로 수렴한다. 모르면 소유 그룹과 발급 시각을 새긴 임시 계보를 발급한다. 접두어로 두 계보를 육안 구분하고 숫자인 사업장 코드와 충돌하지 않게 했다. 임시 계보는 사본 복제와 보급 경로로만 전파된다. 나중에 사업장 코드를 얻으면 승격 스윕이 그룹과 장소와 운송 스냅샷까지 전 테이블을 일괄 갱신해 공식 계보에 합류시킨다. 승격의 방아쇠는 셋이다. 국가 시스템 연동 등록, 시설 코드 손입력, 온보딩.

이 값을 쪼개는 코드는 만들지 않는다. 안에 든 그룹 키는 발급 흔적일 뿐이고 소유는 아래 판별식으로만 판정한다. 파싱 코드가 0건이고 비교가 전부 동등 비교라 기존 무작위 값과 공존해도 아무것도 깨지지 않는다.

시설 판별은 조건식 하나로 한다. "계보 코드가 어떤 그룹과 이어지는 장소"가 그 그룹의 시설이다. 매핑 행의 존재가 담던 소속 정보를 조건식이 대체하므로 새 컬럼도 새 테이블도 필요 없다.

정체성의 정본은 그룹이고 장소는 승계만 한다. 요청이나 복제 원본이 무엇을 담고 왔든 항상 그룹의 값으로 덮는다. "비었을 때만 채운다"로 타협하면 장소만 값을 든 조합이 남아 나중에 다시 갈라진다.

이 규칙의 핵심은 예외를 0으로 만드는 것이었다. 자사 시설만 각인을 건너뛰는 예외가 하나 남아 있었다. 하나쯤은 괜찮겠지 싶었다. 안 괜찮았다. 그 하나 때문에 자사 수기 등록 시설이 판별식 밖으로 빠졌다. 예외를 없애는 대가로 "그룹당 대표 사업장 하나"라는 제약을 받아들였다.

계약도 같은 정체성이면 한 계보다. 같은 당사자 조합의 계약이 이미 있으면 해지나 만료 상태여도 새 계보 대신 그 계보의 다음 리비전으로 발급한다. 갱신과 신규를 코드가 구분할 근거가 생긴다. 남남처럼 쌓이던 중복 계약도 구조적으로 차단된다.

전환 순서가 중요했다. 먼저 각인 예외를 없애 판별식을 성립시켰다. 그 다음 운영과 개발 데이터를 전수 실측해 검증했다. 시설 수백 건이 전부 판별식으로 유도됐고 시설이 아닌 장소 수백 건은 전부 무연결이었다. 예외는 0이었다. 그다음에야 매핑 계층 약 1,000줄을 코드에서 걷어냈다. 테이블 삭제는 접근 0건을 관측한 뒤 별도 배포로 미뤘다.

한 줄기로 모은 뒤

소유와 판별 로직은 조건식 하나로 수렴했고, 중복 계약은 구조적으로 차단됐다. 매핑 계층 약 1,000줄이 사라졌다. 국가 시스템의 사업장과 우리 테넌트의 시설이 같은 뿌리로 이어지면서, 인계서 매칭은 사업장 코드 하나로 상대를 찾는다.

대가는 제약과 규율이다. 한 그룹에 대표 사업장 하나로 접히고, 각인에 예외를 두지 않는 규율을 계속 지켜야 한다. 승격 스윕이 전 테이블을 건드리는 운영 부담도 남았다. 한 조직이 여러 사업장을 각자의 계보로 가져야 하는 요구가 생기면, 그룹당 계보 하나라는 전제부터 다시 설계해야 할 것이다.

뿌리를 모르는 시설은 허용하되, 뿌리를 알게 된 순간 하나로 합친다. 정체성의 정본은 한 곳이고 나머지는 승계만 한다. 판별식은 예외를 하나 허용하는 순간 전체가 무너지기 때문에, 예외 제거를 리팩터링보다 먼저 했다.

이 글 공유
LinkedInThreadsX