Особенности файловых систем, с которыми мы столкнулись при разработке механизма синхронизации Облака Mail.Ru
Одна из основных функций десктопного клиента Облака Mail.Ru — синхронизация данных. Ее целью является приведение папки на ПК и ее представления в Облаке к одинаковому состоянию. При разработке этого механизма мы встретились с некоторыми, с первого взгляда, достаточно очевидными особенностями различных файловых и операционных систем. Однако если о них не знать, можно столкнуться с довольно неприятными последствиями (не получится загрузить или удалить файл). В этой статье мы собрали особенности, знание которых позволит вам правильно работать с данными на дисках и, возможно, убережет от необходимости срочного хотфикса.
1. События от файловой системы не гарантируют полную картину произошедшего
Любой механизм синхронизации директорий требует мониторинг изменений состояния файлов и папок. Благо API каждой операционной системы предоставляет нам такую возможность. Мы используем ReadDirectoryChangesW для Windows, FSEventStream для macOS и inotify для Linux. И уже тут подстерегают неприятные моменты. Дело в том, что под macOS нельзя с уверенностью сказать, какое именно событие пришло от файловой системы. Вы запросто можете получить CREATED, DELETED, RENAMED, MODIFIED на файл в одном событии. И, вроде бы, все логично: если есть удаление, значит файла уже нет, однако:
$ rm 1.txt && echo "hello" > 1.txt
придет одним событием:
1.txt: CREATED | REMOVED | MODIFIED
Поэтому приходится использовать дополнительные механизмы проверки событий для понимания, что именно произошло с файлом или директорией.
В inotify очередь событий может переполниться, и можно начать терять их до того момента, пока вы не заберете некоторые эвенты из очереди. При этом потерянные события вам никак не компенсируются, и нужно будет выполнять дорогостоящие операции вроде обхода по диску.
2. С символическими ссылками не получится работать как с обычными файлами
Символические ссылки могут быть зацикленными: A -> B -> C -> B. Решить эту проблему можно, например, с помощью номера inode (уникальный номер файла или папки в текущем разделе диска, но о них чуть ниже). В нашем случае мы храним список inode символических ссылок, по которым прошли до текущей директории. Если inode текущей символической ссылки совпадает с тем, что уже есть в списке, то считаем ее зацикленной и пропускаем.
Символическая ссылка может оказаться битой. Если в какой-то момент контент, на который указывала символическая ссылка будет, перемещен или удален, то ссылка станет недоступной. Важно правильно обработать этот момент.
Если вы подписаны на событие от директории, в которой у вас есть символические ссылки на другие директории, то события об изменении контента по символической ссылки приходить не будут.
3. Имена файлов и папок могут быть в неправильной UTF-16
Был один интересный баг. В локальном дереве пользователя, который зарепортил нам проблему, был файл. Однако при попытке его чтения мы понимали, что файла нет. Вроде бы логичная ситуация, когда в момент нашей работы файл удаляется. Но при следующем листинге директории файл опять был на месте. Дело в том, что под Windows можно создать невалидную кодировку UTF-16. Точнее, название может содержать невалидную суррогатную пару. Конвертировать такое название в UTF-8, а затем обратно в UTF-16 стандартными средствами (WideCharToMultiByte, MultiByteToWideChar) не получится. Возьмем пример:
wchar_t name[] = < 0xDCA9, 0x2E, 0x74, 0x78, 0x74, 0x00 >;
Суррогатные пары состоят из High и Low значения и нужны для того, чтобы расширить диапазон кодируемых символов. High Surrogates лежат в диапазоне xD800 — xDB7F. Low Surrogates в диапазоне DC00 — DFFF. В нашем названии мы взяли High, но не взяли Low. Таким образом, мы получили невалидный UTF-16.
Конвертируем такое название в UTF-8, затем обратно:
wchar_t name2[] = < 0xFFFD, 0x2E, 0x74, 0x78, 0x74, 0x00 >; // "�.txt"
Символ, представляющий начало суррогатной пары, ломается. Обратиться по такому названию уже не получится.
Код примера
#include #include #include std::string utf16ToUtf8(const std::wstring& utf16) < int size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), NULL, 0, NULL, NULL); std::string utf8(size, 0x00); WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), &utf8[0], size, NULL, NULL); return utf8; > std::wstring utf8ToUtf16(const std::string& utf8) < int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), NULL, 0); std::wstring utf16(size, 0x00); MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), &utf16[0], size); return utf16; > int main() < std::wstring original_utf16 = < 0xDCA9, 0x2E, 0x74, 0x78, 0x74, 0x00 >; // Создаем файл с невалидной суррогатной парой HANDLE handle = CreateFileW(original_utf16.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (handle == INVALID_HANDLE_VALUE) < return 1; >CloseHandle(handle); // Преобразуем название в UTF-8 и обратно std::string utf8 = utf16ToUtf8(original_utf16); std::wstring utf16 = utf8ToUtf16(utf8); // Снова пытаемся открыть файл с преобразованным названием handle = CreateFileW(utf16.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (handle == INVALID_HANDLE_VALUE) < // Не имеем доступа до файла assert(original_utf16 == utf16); return 1; >// Сюда никогда не придем CloseHandle(handle); return 0; >
Мы в модуле синхронизации всегда работаем с UTF-8. Получаем от файловой системы события или листинг и преобразуем названия в UTF-8. Сервер также работает с UTF-8. При обращении к файловой системе, мы преобразуем UTF-8 обратно в UTF-16. Проблема была решена запретом синхронизации невалидных UTF-16.
4. Подводные камни при работе с inodes
Долгое время десктопный клиент не поддерживал переименование файлов и папок. Вместо этого событие переименования обрабатывалось удалением файлов в одном месте и созданием в другом. Этот механизм работал довольно долго и стабильно. Дело в том, что удаление файла из облака — это лишь удаление ссылки на этот файл из пользовательского дерева. Сам файл при этом остается еще на какое-то время жить на сервере, чтобы вы потом, например, могли его восстановить из корзины. Таким образом, удаление и создание файла в другом месте обходились лишь отправкой мета-информации на сервер, который просто удалял ссылку на файл из одного места и создавал ее в другом, даже не открывая при этом локальную копию. Однако с появлением общих папок мы стали понимать, что должны обрабатывать именно перемещение (чтобы не потерять признак общей папки и не отключить присоединенную папку).
Одно дело, когда от файловой системы приходит событие переименования. Тут проблем никаких. Тыц-тыц и переименовали. А если приложение выключено? Нужна какая-то информация, по которой мы будем детектировать событие переименования. Было несколько вариантов детектирования перемещения:
- Сравнивать иерархию файлов и папок. Весьма тяжелый процесс, даже исходя из того, что деревья хранятся в оперативной памяти.
- Создавать скрытые файлы со служебной информацией в каждой папке, по которым мы будем понимать, куда папка переместилась или во что переименовалась. Однако это вызывает некоторые сложности, включая то, что пользователь может менять и редактировать эти служебные файлы, что может привести к неприятным последствиям. Да и «следить» в каждой директории не хотелось.
- Inodes. На этом варианте мы и остановились.
Чуток более человечное описание «как это работает» рекомендую почитать в этой статье. В POSIX получаем inode из stat (st_ino), в Windows — GetFileInformation (nFileIndex). И все, вроде бы, просто:
- Клиент перезапускается, подгружаем закэшированное представление файловой иерархии.
- Сравниваем с тем, что сейчас лежит на диске по факту.
- Находим узлы, номера inode которых отсутствуют в месте, где мы полагаем, но есть в каком-то другом месте.
- Перемещаем эти узлы.
4.1. Хардлинки
Каждая ссылка данного типа на один файл имеет одинаковый номер inode. Мы не детектим переименование, если в дереве есть хардлинки. Хардлинк нельзя создать на папку (ну или почти нельзя), потому особенных проблем тут нет.
4.2. Inodes могут работать иначе, чем вы ожидаете
На некоторых файловых системах номера inode присваиваются не так, как должны (ну или как нам кажется, что должны). Мы полагаем, что их номера при переименовании файла не изменяются. Также мы предполагаем, что если последний файл на ФС с inode 9 удалить, то следующий файл будет иметь inode номер 10. К сожалению, некоторые файловые системы с этим не согласны.
Под macOS на FAT создаются новые файлы (не папки) с inode номер 9999… При переименовании этих файлов номер inode не меняется. При редактировании этих файлов номера меняются на порядковые значения, которые мы и ожидаем увидеть:
$ touch 1.txt $ ls -i 999999999 1.txt $ echo "hello" > 1.txt $ ls -i 223 1.txt
Ext4. Дело в том, что если на этой файловой системе (которая является стандартной в большинстве дистрибутивов Linux), удалить файл с inode номер 9 в одном месте и создать новый файл в другом месте, он будет иметь inode с номером не 10 или выше, а 9.
$ touch 1.txt $ ls -i 270 1.txt $ rm 1.txt && touch 2.txt $ ls -i 270 2.txt
Т.е. на данной файловой системе номером inode становится первый свободный номер. Это немного сломало нам логику. Решение пришло само собой: если задетектили переименование папки, сравниваем для ее контента номера inode для папок и хэш + размер для файлов. Если директории совпадают на 70% и выше — переименовываем. Для файлов — если хэш + размер совпали.
С учетом того, что нумерация inodes в разных файловых системах работает по-разному, у нас есть проверка, работают ли inode так, как мы ожидаем: при запуске модуля синхронизации воспроизводится тестовое поведение для проверки. Если оно такое, как мы ожидаем, значит с номерами inode можно работать. Иначе — продолжаем без поддержки переименования.
5. Программы хранят много служебных файлов на диске
Операционные системы и разной популярности программы используют служебные файлы на диске, синхронизировать которые нет особого смысла. Ниже приведен список файлов и масок, которые, как мы посчитали, нужно игнорировать:
- desktop.ini — хранит пользовательские настройки для текущей директории;
- Thumbs.db — кэши эскизов для изображений;
- файлы, начинающиеся с «~$», или «.~», или начинающиеся с «~» и заканчивающиеся «.tmp» — довольно распространенный шаблон временных файлов. Файлы такого шаблона также создает Microsoft Office при редактировании документов.
- .DS_Store — аналог desktop.ini под Windows;
- Icon\r — достаточно интересный файл, при листинге файл отображается как «Icon?», хранит информацию о изображении на директории, в которой находится;
- файлы, начинающиеся с «._» — достаточно много было шаблонов вместо этого, однако, разнообразным ПО больше нравится использовать свой формат временных файлов, после чего и было решено игнорировать файлы по данной маске.
- .directory — аналог desktop.ini под Windows и .DS_Store под macOS, актуально для некоторых оконных менеджеров.
6. Особенности путей в Windows до файлов и папок
Пути под Windows, безусловно, заслуживают отдельного внимания. Для путей, превышающих значение MAX_PATH (260 символов), нужно использовать перефикс «\\?\». Данный префикс, кстати, нужно использовать для CreateFile, если вы собираетесь открыть COM-порт.
Windows для каждого файла или папки, название которых длиннее 8 символов, создает короткие альясы (еще называются «8.3»). Альясы всегда в высоком регистре, содержат знак «~», за которым идет цифра, увеличивающаяся, если такой альяс уже занят (Например: «C:\PROGRA~1\»). Содержание этих признаков необходимо, но не достаточно, чтобы понять — перед вами обычное название или короткий альяс. WinApi умеет превращать короткие пути обратно в длинные (GetFullPathName). Однако нужно помнить, что он не превратит путь в длинное представление, если такой файл уже не существует.
Если кто-то откроет файл с помощью CreateFile, используя короткий путь и модифицирует его, то в событии от файловой системы (с помощью ReadDirectoryChangesW) вам придет такой же короткий путь. В связи с этим мы стараемся превратить их в длинные как можно скорее. Кстати, вы можете увидеть альясы, если введете «dir /x» из нужной директории в командной строке Windows.
Еще одной неприятной особенностью, которую нельзя пропустить: файлы и папки с точкой в конце нельзя открыть с помощью проводника (справедливо для Windows 7):
7. Заключение
Для каждой файловой системы алгоритм синхронизации пришлось адаптировать соответствующим образом. Лучшим решением в нашем случае было воссоздание тестового окружения при старте для проверки той папки, которую выбирает пользователь. И если тесты не проходят, то мы узнаем новую особенность, а пользователю либо запрещаем работать с данной папкой, либо отключаем какой-то функционал. Надеюсь, особенности, с которыми мы столкнулись, помогут вам избежать трудностей при работе с файловой системой.
Если у вас есть вопросы или замечания, смело задавайте их в комментариях или пишите лично мне на a.skogorev@corp.mail.ru.
- облако mail.ru
- файловые системы
- синхронизация
- Блог компании VK
- Анализ и проектирование систем
- Клиентская оптимизация
- Алгоритмы
Что означает слово «невалидный»?
Валидность —проверенность, стандарт, точность, соответствие требованиям инаоборот.
Остальные ответы
недостоверный, неправильный
Похожие вопросы
Ваш браузер устарел
Мы постоянно добавляем новый функционал в основной интерфейс проекта. К сожалению, старые браузеры не в состоянии качественно работать с современными программными продуктами. Для корректной работы используйте последние версии браузеров Chrome, Mozilla Firefox, Opera, Microsoft Edge или установите браузер Atom.
Как выгружать невалидные данные по задачам
Из выполненных задач можно выгрузить строки, обработка которых завершилась с ошибкой.
Это позволяет вместо фильтрации в файле с результатом сразу получить ошибочные строки, чтобы их проанализировать, отредактировать и корректно перезалить.
- Находим по фильтру нужную задачу и нажимаем на «Выгрузить невалидные данные»:
Ставится задача на выгрузку:
Получаем файл только с ошибочными строками:
- Редактируем данные, удаляем столбцы «Результат» и «Сообщение»:
- Используем этот файл для нового импорта.
Готово!
*Выгрузку можно сделать и по нескольким задачам одновременно.
Главное, чтобы задачи были:
- одного типа
- в статусе «выполнена»
Валидация базы: что это и как работает — простыми словами
Валидация в email-маркетинге — это проверка качества email-адресов на пригодность для рассылок. Адреса бывают несколько типов: рассмотрим каждый, чтобы не путаться.
Валидные — существующие и правильно написанные адреса без подозрительной активности. Короче — адреса, которым можно отправлять рассылки.
Невалидные — адреса с ошибками в домене (@mail.ru) или префиксе (name@). Ещё к невалидным относят: несуществующие адреса, дубликаты, временные и одноразовые ящики, спам-ловушки.
Условно валидные — действующие и правильно написанные адреса, которые сейчас не могут получать рассылки. Почему не могут получать? На это есть ряд причин: не будем перечислять все, лучше рассмотрим на примере.
Пример: адрес рабочий, но ящик получателя переполнен. Логично, что доходить до адресата рассылки не будут. Когда адрес снова будет доступен для получения новых рассылок — он опять станет валидным.
Как в базу попадают условно-валидные адреса? Самые частые случаи:
- Если контакты собирались в офлайне — через анкеты, опросы, флаеры и другие формы.
- В форме подписки не настроили систему Double Opt In (двойное подтверждение подписки).
- База была устаревшей: удалённая или забытая почта, отписавшиеся подписчики — всё в таком духе.
Цели валидации
Любая база, которая «пылится» на дальней полке несколько месяцев, может содержать в себе некорректные адреса. Причём много. Если в базе «невалидов» будет больше 15% от % всех подписчиков — ждите проблем с доставляемостью во «Входящие», блокировками в сервисе рассылок и испорченной репутации домена.
! Как раз в таких случаях валидация email-адресов — обязательный этап перед отправкой рассылки. Валидация уменьшает риск возникновения проблем на старте запуска.
Виды валидации
Валидация email может осуществляться при вводе адреса (например, в форме подписки), путём автоматической проверки соответствия его базовым условиям — наличие @, домена электронной почты — или даже с помощью интеграции со специальными сервисами валидации (глубокая проверка в реальном времени).
Пример проверки: есть список правильного написания распространённых имён. Если человек ошибся при регистрации и написал «Алксей», скрипт проверит его имя, сверит с базой, исправит на «Алексей» и занесет правильный вариант в базу платформы. Да, крутые письма можно отправлять, даже если человек ошибся при вводе.
Ещё пример: валидация базы задним числом (стандартизация базы), когда уже накопилось какое-то количество контактов, нуждающихся в проверке и исправлении ошибок.
Как валидировать email-адреса
Базу можно проверить вручную, но это долго и неэффективно: вы сможете поправить очевидно плохие имейлы и удалить дубликаты. Проверку базы лучше доверить автоматическим сервисам — сэкономите нервы и время.
По порядку: как работаем с такими сервисами:
- Регистрируем аккаунт в выбранном сервисе.
- Загружаем файл с базой контактов, которые хотим провалидировать.
- Запускаем проверку и получаем результат. По времени — от нескольких минут до часов или даже дней (зависит от размера базы).
В готовом файле сервис добавит к каждому email-адресу свою оценку — стоит ли делать на него рассылку или нет.
А тут — экспресс-проверка базы:
Проверка платная — как правило, основанная на числе контактов в базе. Иногда владельцы бизнеса / руководители не хотят / не готовы платить деньги за проверку. Скажем так: экономия может обернуться бОльшими проблемами.
Оплата валидации в данном случае — инвестиция в базу, чтобы не наломать дров в будущем.
Прогрев базы
Прогрев базы — это отправка писем частями с постепенным наращиванием единовременных отправок. Не путайте с валидацией: это разные процессы.
Чем полезен прогрев базы? С ним не теряются адреса писем при переезде с одной платформы на другую + он сохраняет репутацию домена на хорошем уровне.
Начинайте прогрев с активных сегментов базы (те, с кем уже взаимодействовали). Если таких нет (email-маркетинг на стадии внедрения), то делайте рассылку по проверенным адресам.
Пример прогрева базы до 50 000 подписчиков:
1 000 — 2 000 — 4 000 — 6000 — 9 000 — 12 000 — 15 000 — 20 000 — 25 000 — 30 000 — 35 000 — 40 000 — 45 000 — 50 000
Если база содержит более 50 000 подписчиков — нужно разделить их на сегменты по доменным семействам (@gmail, @yandex, @mail) и прогревать по примеру выше.
! Прогревать базу нужно не только на старте, но и в качестве профилактики, если по ней давно не запускались рассылки. Первая отправка может дать не самые приятные результаты: высокий процент ошибок на доставки. Не пугайтесь, это нормально.
Ориентируйтесь на статистику и смотрите за доставляемостью. Если ошибок мало — можно увеличить единовременные отправки. Нормальный показатель ошибок и жалоб при отправке рассылок — до 1%. Значит, проблем с базой нет. Если 2–5%, значит, часть адресов невалидна и нужно реанимировать базу контактов.
Сервисы для проверки адресов
Выбираем, отталкиваясь от происхождения базы. Если это отечественная почта: Mail, Yandex, Rambler — то лучше тоже проверять её на отечественном сервисе. Почему? Просто потому, что она лучше работает с такими типами адресов.
Если почта «западная»: Gmail, Yahoo, Aol и т.п. — рекомендуем выбирать «западные» сервисы. Причина та же.
А сейчас разберёмся с механикой проверки. Чтобы понимать, как работает сервис.
Обычно процесс ограничивается проверкой синтаксиса (@, домен почты и т.д.) и других технических параметров. Продвинутые сервисы умеют больше: они сверяются с большими базами известных невалидных адресов. Понятно, что продвинутые — лучше.
Общее обсудили — теперь к сервисам.
Mailvalidator — онлайн-платформа для контроля качества контактной базы. Список имейлов можно загружать файлом, возможно подключение к сервису по API.
В диагностику входит:
- синтаксис;
- дубликаты;
- спам-ловушки и контакты, от которых часто поступают жалобы;
- несуществующие и неактивные домены;
- отказы (bounce) по каждому адресу.
Чем хорош: двумя видами проверки. Экспресс для имейл-адресов (с доступной почтовой историей) и полная проверка для всех остальных. Визуализированные отчёты в виде графиков, персональные рекомендации по улучшению качества контактной базы, интерфейс на русском.
Mailvalidator как встроенный инструмент используют и сами ESP-платформы. Например, Mailganer.
Цена за проверку = количество имейлов. Чем их больше — тем выгоднее.
Zero Bounce — онлайн-верификатор, работает с файлами в формате TXT и CSV.
Что умеет:
- удалять адреса с ошибками доставки (hard/soft bounce);
- очищать от спам-ловушек и контактов, с которых поступает много жалоб;
- искать дополнительные данные.
Чем хорош: сервис находит недостающую информацию по имейлам (имя, фамилию, пол, город, страну, IP), есть круглосуточная поддержка.
Ещё: бесплатный тариф, если адресов немного. Дальше — уже по подписке + можно настроить кастомно в зависимости от нужд бизнеса (вплоть до Enterprise с безлимитным тестированием за 999$).
Snov.io — сервис по очистке списков email-адресов в режиме реального времени.
Удобен: можно удалить все catch-all и невалидные адреса, загрузить список адресов файлом, воспользоваться веб-приложением или подключить Email Verifier к CRM по API. Больше удобств: можно добавлять и верифицировать адреса посредством расширения Email Verifier для Chrome.
Что умеет:
- проверять адреса и домены на catch-all;
- работать с синтаксисом;
- исследовать адреса на случайный набор символов;
- проверять существующий домен;
- проверять MX-записи;
- запускать SMTP-аутентификацию;
- проверять на freemail;
- удалять дубликаты.
Чем хорош: индивидуальная проверка, импорт списков адресов для верификации и экспорт результатов проверки в удобном формате, интеграция через API с CRM-платформами, большой выбор тарифов.
Нюансы валидации
Узнаем, чем могут огорчить сервисы валидации. Парочка моментов, о которых вы тоже должны знать:
- Платить придётся за каждый адрес, отправленный на проверку. Поэтому рекомендуем подготовить базу заранее и избавиться от дублей, абракадабры, которая иногда встречается среди нормальных email-адресов. Все можно сделать своими руками — небольшие манипуляции в Excel-таблице, и всё готово.
- Сервис может не пропускать некорректные адреса: маленький процент погрешности на больших базах допустим. Ничего страшного если сервис отсеял 30–50% адресов, такое бывает если база «сильно залежалась».
- С рассылкой по свежевалидированной базе не нужно затягивать, чтобы результаты валидации не потеряли актуальность.