Кратко

  • RFC 9738 разрешает серверу обработать не более N сообщений за одну команду, двигаясь от старших UID к младшим, и вернуть OK вместе с MESSAGELIMIT и нижней границей обработанного участка.
  • COPY и MULTIAPPEND сохраняют атомарность и при превышении лимита не выполняют ничего; SEARCH, FETCH, STORE, MOVE и UID EXPUNGE способны оставить достоверный частичный результат или реальное частичное изменение.
  • Объявленный предел — условие координации, а не доказательство строгого enforcement, поддержки всеми клиентами, неподвижности ящика или завершённого пользовательского результата.

Опасность последней тысячи в том, что она выглядит правдоподобно. Письма идут по времени, флаги согласованы, новые элементы на месте. Интерфейс может написать «синхронизация завершена». Пользователь не знает, что начало недели не проверялось.

RFC 9738 делает остаток видимым на уровне протокола. MESSAGELIMIT N LastUID сообщает предел работы за один вызов и самый младший UID, достигнутый в этом проходе. Но сохранить этот смысл должен клиент. Если он оставит только финальный OK, протокол уже не сможет вернуть выкинутую информацию.

N ограничивает команду, а не ящик

Сервер объявляет MESSAGELIMIT=N для SEARCH, FETCH, STORE, COPY, MOVE, их UID-вариантов, а также APPEND и UID EXPUNGE. Более узкая возможность SAVELIMIT=N применяется, когда ограничиваются только семейства COPY и APPEND. Рекомендуемое значение не должно быть меньше тысячи.

N не означает число сообщений в ящике. Это не quota, не срок хранения и не максимальный размер письма. Число задаёт объём обработки одного вызова.

Для SEARCH учитываются просмотренные сообщения, а не только совпадения. Сервер может проверить тысячу и вернуть восемь UID. Восемь совпадений верны для просмотренного участка, но ничего не говорят о более старом остатке. Если превратить их в общий счётчик, ошибка окажется не в арифметике, а в знаменателе.

При возврате кода сервер обязан идти от наибольшего UID к наименьшему. LastUID обозначает нижний край завершённого участка. Он помогает сформировать продолжение, но не является snapshot, количеством оставшихся сообщений или гарантией, что указанный UID будет существовать позднее.

Между проходами приходят новые письма и происходят expunge. А если меняется UIDVALIDITY, прежняя отметка относится к другой генерации идентификаторов. Хранить один UID без выбранного ящика и его поколения опасно.

OK подтверждает выполненную часть

FETCH может вернуть данные тысячи сообщений и завершиться OK [MESSAGELIMIT …]. SEARCH возвращает совпадения в просмотренной полосе. STORE уже меняет flags. MOVE может скопировать часть в новый ящик и expunge её из исходного. UID EXPUNGE может окончательно удалить часть сообщений с \Deleted.

Это не предварительный просмотр. Эффект состоялся. Назвать вызов неудачным — значит забыть о нём; назвать всю задачу завершённой — значит забыть остаток. Состояние вызова и состояние исходного намерения должны храниться отдельно.

Продолжение зависит от команды. Для UID STORE диапазон обновляется так, чтобы не включить возвращённый нижний UID. UID MOVE можно повторить с тем же множеством, потому что уже перемещённые UID исчезли из источника. MOVE по sequence number требует пересчёта после expunge. UID EXPUNGE может повторять прежний UID-параметр, пока код предела не исчезнет или не придёт явный NO либо BAD.

Один универсальный retry-loop стирает эти различия. Он способен повторно применить действие, пропустить участок или после перенумерации затронуть другое письмо. Нужны список наблюдавшихся UID, подтверждённые эффекты, нижняя граница и правило построения следующего вызова.

Сигнал может находиться не в последней строке. Если требуется одновременно сообщить EXPUNGEISSUED, он занимает tagged OK, а MESSAGELIMIT передаётся в untagged NO. Парсер, читающий только итоговый status, сохранит успех и потеряет незавершённость. Полный протокол включает tag, все untagged responses, данные, response codes и финальную строку.

Одинаковый предел даёт разные последствия

COPY и UID COPY атомарны. Если множество превышает лимит, сервер отвечает NO [MESSAGELIMIT …] и не копирует ни одного письма. MULTIAPPEND также выполняется целиком либо не выполняется вовсе.

MOVE не обязан быть атомарным. STORE и UID EXPUNGE тоже могут оставить частичный эффект. Поэтому одинаковый код описывает либо полный отказ без изменений, либо успешное изменение части множества. Без имени команды событие невозможно правильно восстановить.

Есть и операции вне этого лимита. Сервер не должен накладывать его на EXPUNGE, CLOSE и STATUS UNSEEN. Их полная семантика в IMAP сохраняется, однако OK всё равно не доказывает долговечность записи на носителе, синхронизацию другого клиента или то, что увидел человек.

RFC 9394 PARTIAL проводит соседнюю границу. Если клиент явно просит PARTIAL-окно больше N, сервер отказывает и ничего не делает. Похожий FETCH без PARTIAL может вернуть неявный частичный участок. Запрошенная страница и принудительное усечение по-разному отвечают на превышение одной мощности.

Сохранённый поиск сохраняет и пробел

SEARCHRES позволяет сохранить результат SEARCH в $. Если исходный поиск встретил MESSAGELIMIT, сохранённое множество тоже должно быть усечено. $ содержит истинные совпадения из просмотренной полосы, но не все совпадения исходного диапазона.

Позднейший STORE или COPY по $ может полностью завершиться без нового кода лимита. Его input уже достаточно мал. Тем не менее он родился из неполного поиска. Поздний успех не расширяет ранний знаменатель.

Поэтому сохранённому результату нужна история: ящик, UIDVALIDITY, запрос, исходный диапазон, предел, нижний UID, время и признак необходимости продолжения. Без этих полей удобная переменная выглядит как утверждение о полноте.

Критерии UIDAFTER и UIDBEFORE упрощают запрос следующего участка. Они не останавливают новые поступления, не возвращают удалённые UID и не переходят в новую генерацию. Это язык диапазонов, а не многокомандная транзакция.

Мягкое объявление перед жёстким включением

RFC прямо признаёт проблему старых клиентов. В ящике больше N они могут всегда терпеть неудачу на COPY, видеть только последние N писем в FETCH, SEARCH и MOVE и выдавать неверные counts. Для пользователя это похоже на поломку или исчезновение старой почты.

Поэтому сервер может сначала объявить предел, но не применять его. Поддерживающие клиенты добровольно уменьшают команды и снижают нагрузку. Старые продолжают работать. На следующем этапе можно объявить тысячу как soft limit, но прерывать лишь на скрытом hard limit в десять тысяч. Попытки выше объявленного значения записываются, чтобы понять готовность клиентов.

Строка в CAPABILITY доказывает объявленное правило сессии. Она не доказывает, что сервер уже режет ровно на этой цифре. Строгое применение видно в ответах. Адаптация клиента видна в размере команд и продолжениях. Полнота видна только в итоговой сверке.

Soft и hard limit необходимо измерять отдельно. Команда выше тысячи может принадлежать терпимому старому клиенту, новому клиенту, проигнорировавшему объявление, или периоду до строгого включения. Объединение их в «нарушения» лишает оператора картины реальной миграции.

Нижний UID — шов, а не доказательство целого

Задача по всему ящику сначала фиксирует своё намерение. Для каждого прохода хранятся ящик, UIDVALIDITY, команда, запрошенное множество, класс атомарности, capability, возвращённые UID, эффект, untagged ответы, status, предел и LastUID.

При закрытии сравниваются сообщения, предназначенные к обработке в начале; реально проверенные или изменённые; исчезнувшие во время серии; новые и потому находящиеся вне исходного объёма. Необъяснённая разность остаётся открытой. Последний вызов без кода предела завершает цепочку, но не создаёт задним числом snapshot динамического ящика.

UIDBATCHES планирует ограниченные диапазоны. PARTIAL просит определённую страницу. MESSAGELIMIT сообщает предел, встреченный при исполнении. SEARCHRES хранит множество. QRESYNC помогает отследить изменения. Общее слово «batch» не должно заменять эти разные обещания.

Нижний UID соединяет два прохода, если клиент сохраняет контекст. Он не доказывает целое и не снимает обязанность объяснить остаток. RFC 9738 делает частичную работу честной; полноту создаёт не строка OK, а клиент, который довёл намерение до сверки.

Источники