개요
인증 응답이 유실되면 노드가 자기 문서를 못 찾고 새 문서로 갈라지던 것. 앞선 작업에서 뺐던 ticketb 단계를 POST 매칭에 복원하고, 옛 값을 문서에 되쓰던 구멍 둘을 같이 막았다. id(DeviceID) 단계는 복원하지 않았다.
현상
서버는 DB에 쓰고 나서 응답을 보내고, 에이전트는 응답을 받고 나서 새 ticket을 저장한다. 그래서 어긋남은 「서버만 앞서간」 한 방향으로만 생긴다.
- 실PC: guid가 하드웨어에서 나와 바뀌지 않으므로 2단계(guid 매칭)에서 잡힌다.
- VM: 서버가 인증마다 guid를 새로 만들고 에이전트는 응답을 받아야 그것을 저장한다. ticket과 guid가 같이 어긋나 1·2단계가 모두 빗나가고 문서가 새로 생긴다.
부하로 응답이 연속으로 끊기는 상황이 이 작업의 동기다.
적용한 변경
| # | 내용 | 대상 |
|---|---|---|
| 1 | POST 매칭에 3단계 「요청 ticket == 문서 ticketb」 추가 | Node.find_and_rotate_for_post |
| 2 | 신규 노드 생성 시 요청 ticket을 previous_ticket에 기록하지 않음 | _build_new_node_document |
| 3 | 회전 없는 요청이 저장된 ticket·ticketb·guid를 요청 값으로 되쓰지 않음 | _build_update_fields |
| 4 | 회전이 억제된 POST는 원자 매칭도 하지 않고 조회만 함 | find_and_rotate_for_post(rotate=…) |
| 5 | ticketb로 매칭된 건을 로거 warning으로 기록 | node_auth_case |
1번은 POST 전용이다. 복구가 필요한 재기동에서 에이전트는 POST로 온다(MQTT 미연결 + online). PUT 경로는 원자적이지 않아 넣지 않았다.
2번이 없으면 갈라져 나온 문서마다 이미지의 ticket이 박혀 3단계 열쇠가 문서 수만큼 복제된다.
3번의 판정 기준은 _check_tickets 이전의 헤더 ticket이다. 값 비교만 하면 최초 인증·레지스트리 유실(서버나 에이전트가 값을 새로 만든 경우)까지 되쓰기로 잡혀 회전이 막힌다. 검증 중 발견해 고쳤다.
4번은 계획에 없던 항목이다. 3번의 가드보다 원자 매칭의 $set이 앞서 실행돼 옛 값을 먼저 쓰고 있었다.
1회용(매칭에 쓴 ticketb를 그 자리에서 비우기)은 채택하지 않았다. 에이전트는 응답을 받을 때까지 같은 ticket을 계속 내밀므로, 소비하면 두 번째 재시도부터 받지 못한다.
감수한 것
3단계는 「응답을 놓친 진짜 주인」과 「같은 이미지에서 나온 복제본」을 구별하지 못한다. 서버가 받는 요청이 같다. 복제 이미지 한 벌당 문서 하나가 주인이 바뀔 수 있고, 그 문서의 점유자가 다음 인증을 하면 ticketb가 그 노드의 ticket으로 덮여 통로가 닫힌다.
그 대가에, 같은 이미지 클론 둘이 동시에 POST하면 앞선 쪽이 ticket 단계로 회전하면서 ticketb에 그 이미지 값을 적고 뒤쪽이 그 값으로 3단계 매칭된다. 둘이 한 문서를 잠시 공유한다. 원자 처리 자체는 그대로라 두 회전이 겹치지는 않지만, 「클론이 서로 다른 문서로 갈라진다」는 결과는 더 이상 보장되지 않는다. 공유는 MQTT client id(node-<_id>) 충돌로 한쪽이 끊겨 재인증하면 풀린다.
1회용으로 소비하면 이 보장은 돌아오지만 연속 유실 복구를 잃는다. 둘은 같은 스위치의 양면이라 중간값이 없다.
검증
- 유닛 테스트 2671 passed(신규 11건). 구현을 되돌리는 변이 3회로 테스트가 새 동작에 묶여 있음을 확인.
- 실 MongoDB 12항목: alias(ticketb) 질의, 신규 문서의 필드 부재, 회전/비회전 동작, PUT에 3단계 없음, 필드 부재 문서가 null 질의로 잡히지 않음,
ticketb_1IXSCAN 사용. - 클론 시나리오 실 MongoDB 재현: 클론 셋이 한 문서를 차례로 가져가고(문서 총수 1), 쫓겨난 쪽은 돌아오지 못해 갈라진다. 동시 진입 시 둘 다 같은 문서에 붙는다.
- 배포본 대조: 운영 컨테이너의
node.py·node_auth_case.pysha256이 커밋본과 일치.
실측
node.ticketb중복: 운영 서버 56건·고객사 서버 661건 전수 0(중복이면 3단계가 임의 문서를 집는다).- VM: 운영 서버 32/56, 고객사 서버 0. 3단계의 대상은 VM이므로 효과 확인은 운영 서버에서 한다.
- 고객사 서버 마지막 인증 단계: ticket 615 · guid 2 · ticketb 0 · id 0.
- 운영 서버 배포 후 VM 여러 대 에이전트 재시작: 문서 분열 0건, 전부 1단계 매칭, 되찾기 warning 0건. 평소 경로에는 영향이 없다.
남은 것
node matched by previous ticket건수를 관찰해, 에이전트가 미확인ticket2를 메모리에 들고 함께 전송하는 방식(PENDING)을 도입할지 판단한다. 이 방식은 위의 동시 공유까지 닫는다.- 고객사 서버 문서 분열 별건: 같은 guid 문서 25쌍, 생성 후 guid가 바뀐 문서 90건, 최근 1주 483건 생성.
Orange Platform 노드 인증 경로에서 발견한 VM 문서 분열 문제 분석 리포트입니다.