요약

  • IMAP 메시지 시퀀스 번호는 메시지의 현재 위치를 나타내며, expunge 이후 밀려 이동할 수 있다. UID는 세션을 넘어 유지되지만, 오직 하나의 메일함과 하나의 UIDVALIDITY 세대 안에서만 그렇다.
  • UIDVALIDITY가 바뀐 것은 연속성의 명시적 철회다. 클라이언트는 낡은 캐시 항목과 UID 기반 동작을 버려야 하며, 그렇지 않으면 같은 번호를 물려받은 전혀 다른 메시지를 가져오거나, 플래그를 바꾸거나, 삭제할 위험을 감수하게 된다.

위험한 메시지는 정확히 맞는 번호를 여전히 가진 쪽이었다

메일 클라이언트가 수신함의 오프라인 사본을 들고 기차역을 떠나는 장면을 상상해 보자. 캐시 안에서 UID 4821은 청구서다. 사용자는 연결이 끊긴 상태에서 그 메시지를 삭제 대상으로 표시한다. 클라이언트가 돌아오기 전에 서버는 원래의 UID 할당을 보존하지 못하는 오래된 저장 시스템으로부터 메일함을 복원했다. 재구성된 수신함에는 4821이라는 번호를 달고 있는 또 다른 메시지가 들어 있다.

서버가 아무 일도 없었다는 듯이 행동한다면, 클라이언트의 문법적으로 완벽한 명령은 엉뚱한 메일을 삭제할 수 있다. 인증은 아무 도움이 되지 않는다. 사용자는 권한이 있고, 연결은 보호되어 있으며, 양쪽 모두 문법을 지키고 있다. 오류는 신원에 있다. 폐기된 세대에서 온 유효한 번호가 새 세대에서 유효한 참조로 행세하도록 허용된 것이다.

IMAP의 해답은 무겁고도 절약적이다. 서버는 UIDVALIDITY를 바꾼다. 클라이언트는 불일치를 보고, 해당 메일함의 캐시된 UID 상태를 비우고, 옛 UID를 겨냥한 대기 중 동작을 포기한다. 대역폭과 시간은 잃는다. 그러나 청구서와 새 메시지는 뒤섞이지 않는다.

이 메커니즘은 저장된 상태를 위한 작은 헌법 조항이다. 연속성은 유용할 수 있지만, 그것을 공급해 주던 시스템이 바뀐 뒤에는 당연한 것으로 가정할 수 없다.

목록에서의 자리는 결코 신원이 아니었다

IMAP에는 늘 메시지에 번호를 매기는 두 가지 유용한 방법이 있었다. 메시지 시퀀스 번호는 현재 선택된 메일함에서의 상대적 위치다. 스무 개의 메시지가 들어 있는 메일함에서 값은 1부터 20까지 매겨진다. 덕분에 범위 지정과 산술이 편리하다. 서버가 개수가 17에서 20으로 늘었다고 알리면, 클라이언트는 끝에 세 개의 자리가 생겼다는 것을 안다.

이 편의에는 함정이 있다. 메시지 6을 영구 삭제하면 옛 메시지 7이 메시지 6이 되고, 그 뒤의 모든 자리가 한 칸씩 내려온다. 어느 시점에 한 메시지가 갖던 번호는 나중에 다른 메시지의 것일 수 있다. 1996년 IMAP4rev1 명세인RFC 2060은 이 재할당을 직접 기술한다. 현재의RFC 9051도 같은 구분을 유지한다.

대화형 세션에서는 서버가 변경을 보고하고 클라이언트가 자신의 지도를 갱신할 수 있으므로 상대적 위치로 충분하다. 그러나 연결 끊긴 사용에는 나쁜 기억 열쇠다. 노트북이나 휴대전화가 자리를 비운 사이에 다른 클라이언트들이 메일을 추가하고, 플래그를 바꾸고, 메시지를 expunge했을 수 있다. 돌아왔을 때 “메시지 6”은 오늘 목록의 좌표일 뿐, 어제의 객체에 대한 증거가 아니다.

IMAP4는 메시지에 더 긴 기억을 주었다

1994년 12월에 발표된RFC 1730은 IMAP4를 원격 메일함과 오프라인 재동기화를 중심으로 정의했다. 그 UID는 메일함 내에서 엄격히 오름차순으로 할당되는 32비트 메시지 식별자였다. UID는 연속적일 필요가 없었고, 시퀀스 번호와 달리 세션을 넘어 유지되었다.

이 설계 덕분에 클라이언트는 재접속 때마다 모든 메시지 본문을 내려받아 비교하지 않고도 로컬 상태를 유지할 수 있었다. UID 4821을 이미 본 적이 있다고 기억하고, 초안 동작을 UID 4821에 연결하고, 마지막 방문 이후 무엇이 바뀌었는지 서버에 물을 수 있었다.

그러나 “고유하다”는 말은 저장 계층이 항상 뒷받침할 수 있는 수준보다 많은 신뢰를 불러왔다. 기존 메일 저장소들이 모든 메시지 옆에 영속적인 IMAP 번호를 보존하도록 만들어져 있지는 않았다. IMAP이 아닌 프로그램이 저장소를 재배열할 수도 있었다. 메일함이 삭제된 뒤 같은 이름으로 새 메일함이 만들어질 수도 있었다. 복원이나 마이그레이션은 메일은 남기면서 옛 UID 메타데이터를 잃을 수 있었다.

프로토콜은 완벽한 영속성을 요구하여 그런 시스템들을 배제할 수 있었다. 아니면 조용히 번호를 재활용하게 두어 오프라인 동작을 위험하게 만들 수도 있었다. IMAP은 세 번째 선택을 했다. 관찰 가능한 실패 경계를 갖춘 영속성이다.

UIDVALIDITY는 메일함이 아니라 세대에 이름을 붙였다

모든 메일함은 32비트 UIDVALIDITY 값을 갖는다. 이전 세션의 UID가 더 이상 유지되지 않을 때, 새 값은 반드시 이전 값보다 커야 한다. RFC 1730은 이미 메일함 생성 시각을 그럴듯한 원천 하나로 제안했다.RFC 9051은 증가하는 카운터도 언급한다. 알고리즘 자체는 요점이 아니다. 요점은 서버가 새 UID 세대를 클라이언트가 캐시한 세대와 구별해야 한다는 것이다.

UIDVALIDITY는 종종 전역적으로 고유한 메일함 식별자로 오해된다. IETF의 1999년 구현 권고인RFC 2683은 그 해석을 명시적으로 거부한다. 두 메일함이 같은 UIDVALIDITY를 가질 수 있다. UID는 메일함 안에서만 고유하다. 내구성 있는 참조는 메일함 이름, UIDVALIDITY, UID가 함께 이루는 조합이다.

이 범위는 겉치레가 아니다. Archive와 Inbox가 둘 다 UIDVALIDITY 77을 갖고 둘 다 UID 4821을 담고 있다면, 그 값들은 서버 전역에서 하나의 메시지를 가리키지 않는다. 마찬가지로 UID가 32비트라고 해서 전역적으로 의미를 갖는 것도 아니며, 두 개의 32비트 필드를 이어 붙인다고 보편적 신원이 생기는 것도 아니다.

RFC 3501과 현재 기본 명세는 적절한 범위 안에서 더 강한 불변식을 부과한다. 그 조합은 해당 서버에서 하나의 불변 메시지를 계속 가리켜야 한다. 메시지 본문, 엔벨로프, 내부 날짜, 크기, 구조가 같은 조합 아래 조용히 다른 내용이 되어서는 안 된다. 가변 플래그는 제외되는데, 플래그를 바꾸는 것은 정상적인 IMAP 동작의 일부이기 때문이다. expunge된 UID는 같은 UIDVALIDITY 세대에서 재할당될 수 없다.

정직함은 위장보다 비용이 컸다

UIDVALIDITY의 변경은 파장이 크기 때문에, 서버들은 UID를 보존하도록 강하게 권고되었다. 그러나 표준들은 어려운 사례를 숨기지 않았다. 백엔드에 영속적인 UID 저장소가 없다면, 매 세션마다 새 UID와 새 UIDVALIDITY를 생성해야 할 수 있다.RFC 4315의 UIDPLUS 확장은 영속성을 보장할 수 없는 레거시 저장소를 위한 UIDNOTSTICKY까지 정의하면서, 새 저장소는 그 상태를 피해야 한다고 말한다.

많은 인터넷 프로토콜에서 보이는 동일한 절충이다. 정상적인 경우를 효율적으로 만되, 불변식이 깨지는 경우를 위한 정직한 탈출로를 남겨두는 것이다. 그 탈출로는 보여야 한다. UID 연속성을 잃었으면서 옛 UIDVALIDITY를 그대로 둔 서버는 더는 보증할 수 없는 상태에 대해 거짓말을 함으로써 동기화 한 사이클을 아낄 수 있다.

RFC 2683은 최악의 결과를 담백하게 기술한다. 낡은 UID를 쓰는 클라이언트는 엉뚱한 메일을 삭제할 수 있다. 위험은 삭제에 국한되지 않는다. 낡은 동작은 다른 메시지에 플래그를 달거나, 복사하거나, 이동하거나, 가져올 수 있다. 클라이언트가 오프라인 의도를 충실하게 재생할수록, 거짓 연속성의 피해는 커진다.

무효화는 로컬 권한을 박탈했다

2006년 연결 끊긴 IMAP 클라이언트를 다루기 위해 나온RFC 4549는 UIDVALIDITY를 운영상의 결정 사항으로 만든다. 메일함에 동작을 재생하기 전에 클라이언트는 현재 값을 확인한다. 캐시된 값과 다르면, 클라이언트는 그 메일함의 로컬 캐시를 비우고, 옛 UID를 참조하는 대기 중 동작을 제거하며, 실패로 처리해야 한다.

이 대응은 낭비처럼 느껴질 수 있다. 클라이언트는 여전히 올바른 메시지 본문을 갖고 있을 수 있다. 마이그레이션 후 대부분의 UID가 우연히 같은 메일에 대응할 수도 있다. 프로토콜은 닮음으로부터 신원을 추론하기를 거부한다. 서버가 세대가 바뀌었다고 선언하는 순간, 클라이언트는 자신이 저장한 번호가 같은 객체의 이름이라는 증거를 상실한다.

무효화는 의도적으로 국소적이다. 한 메일함의 UID 문제가 모든 메일함을 지울 필요는 없다. RFC 2683은 하나의 전역 서버 범위 풀에서 UID를 할당하지 말라고 경고하는데, 부분적으로는 그 때문이다. 전역 풀은 32비트 공간을 더 빨리 소모하며, 한 번의 고갈 사건을 시스템 전역의 UIDVALIDITY 재설정으로 번지게 할 수 있다. 메일함 범위는 이름공간과 피해 반경을 함께 좁혀 준다.

URL은 자신만의 부실 검사를 실어 나를 수 있었다

같은 원리는 동기화 엔진 밖으로도 나갔다. 1997년 발표된RFC 2192는 메일함, UIDVALIDITY, UID를 담을 수 있는 IMAP URL을 정의했다. URL을 여는 프로그램은 저장된 유효성 값을 서버의 현재 값과 비교하여 그 참조가 부실해졌는지 판단해야 한다.

이 세부 사항은 UIDVALIDITY의 더 깊은 기능을 드러낸다. 그것은 메시지가 만료될 때를 알려주는 타이머가 아니다. 서버가 정직하다는 증명도 아니다. 그것은 이전에 발급된 식별자가 여전히, 그 식별자가 의미를 갖던 세대 안에 있는지에 대한 증거다.

벌거벗은 숫자는 쉽게 이동한다. 안전한 참조는 자신이 이동을 멈춰야 할 때를 알 만큼 충분한 문맥을 갖고 다닌다.

더 빠른 동기화조차 신원에서 시작했다

메일함이 커지고 이동 연결이 불규칙해지면서, 전체 비교는 비싸졌다.RFC 7162는 CONDSTORE와 QRESYNC를 결합하여 클라이언트가 바뀐 플래그와 expunge된 UID를 효율적으로 알 수 있게 했다. QRESYNC의 VANISHED 응답은 사라진 메시지를 압축적으로 보고하며, 재접속하는 클라이언트는 종종 한 번의 왕복으로 동기화할 수 있다.

클라이언트가 QRESYNC에 제공하는 첫 상태 값이 마지막으로 알던 UIDVALIDITY다. 그것이 일치할 때만, 그 뒤의 델타 상태, 즉 수정 시퀀스와 알고 있는 UID들이 지금 열린 메일함 세대에 속한다. 불일치하면 서버는 그 이후 매개변수들을 무시한다. 정교하고 정확한 변경 목록도 잘못된 신원 공간을 기술하고 있다면 쓸모가 없다.

따라서 효율은 유효성 아래에 놓인다. 수정 시퀀스는 “무엇이 바뀌었는가?”에 답한다. UIDVALIDITY는 먼저 “이것들이 여전히 같은 것들인가?”에 답한다.

30년 뒤, 상대 번호를 끌 수 있게 되었다

2024년 5월,RFC 9586은 실험적 UIDONLY 확장을 정의했다. 클라이언트가 이를 활성화하면 세션은 더 이상 명령에서 메시지 시퀀스 번호를 받아들이지 않고 응답에서도 반환하지 않는다. UID 기반 형식이 그 자리를 대신하며, expunge는 VANISHED로 나타난다. 클라이언트와 서버는 이동하는 위치와 안정적인 UID 사이 지도의 일부를 유지하지 않아도 되게 된다.

UIDONLY는 필수가 아니며, RFC는 모든 서비스가 이를 배포한다고 주장하지 않는다. 그래도 그 방향은 시사적이다. IMAP4가 UID를 도입한 지 30년 후에도 프로토콜 작업은 상대적 위치에 대한 의존을 줄이고 있었다. 그러나 UIDONLY가 UID를 영원한 것으로, 서버 전역으로, 또는 메일함 사이에서 이동 가능한 것으로 만들지는 않는다. 세대 경계는 그대로 남는다.

진전은 작은 숫자에서 큰 숫자로 온 것이 아니다. 모호한 좌표에서 명시적으로 범위가 지정된 증거로 온 것이다.

식별자가 살아남은 이유는 프로토콜이 실패를 허용했기 때문이다

IMAP의 지속된 성과는 결코 바뀌지 않는 마법의 숫자가 아니었다. 그것은 책임의 분할이었다. 메시지 저장소를 통제하는 서버는 UID 연속성이 살아남았는지 말해야 한다. 캐시와 대기 의도를 통제하는 클라이언트는 세대가 바뀔 때 신뢰를 철회해야 한다. 어느 쪽도 편의로 연속성을 만들어 낼 수 없다.

그것이 가장 유용한 형태의 얇은 조율이다. 공유 프로토콜은 단일 저장 엔진, 마이그레이션 방식, 클라이언트 데이터베이스를 규정하지 않는다. 그것은 불변식, 실패 신호, 안전한 대응을 정의한다. 운영 중인 시스템들은 서로 다를 자유를 유지하지만, 그 자유로 오래된 식별자가 조용히 새 객체를 명령하도록 만들 수는 없다.

UID는 세션을 넘어 살아남을 수 있다. UIDVALIDITY가 그것이 재생을 넘어 살아남지 못한 순간을 알려 줄 수 있기 때문이다.

출처와 한계

영속적인 IMAP UID와 UIDVALIDITY의 1994년 기원은 RFC 1730에서 오며, 시퀀스 번호의 구분과 신원 불변식은 RFC 2060, RFC 3501, RFC 9051에서 온다. RFC 2683은 구현 교훈을, RFC 2192는 부실 URL 검사를, RFC 4315는 레거시 UIDNOTSTICKY 신호를, RFC 4549는 연결 끊긴 클라이언트의 대응을, RFC 7162는 QRESYNC 의존성을, RFC 9586은 실험적 UIDONLY 방향을 제공한다. 이 문서들은 현재의 배포 점유율, 제품 동작, 메시지 진위성, 암호학적 무결성, 또는 UIDVALIDITY를 구성하는 보편적 방법을 입증하지 않는다.