Компания
650,01
рейтинг
11 апреля 2013 в 12:44

Разработка → Как мы делали Яндекс.Диск: серверная сторона, WebDAV и Erlang

На прошлой неделе Яндекс.Диску исполнился год, и за этот год сервисом успели воспользоваться уже больше 8 000 000 пользователей.

А сейчас мы продолжаем рассказывать о том, сколько усилий понадобилось, чтобы всё это стало возможным. Недавно мы писали о том, как и почему команда Яндекс.Диска выбрала WebDAV для синхронизации десктоп-клиентов с сервером и начала работу над прототипом клиента Яндекс.Диска. Сегодня, как и обещали, — о том, как всё работает с серверной стороны.

Диск спасает файлы — не Шойгу

Для правильной синхронизации нужно не только уметь заливать файлы, но и реанимировать заливку в случае прерванного соединения, а также научить клиент учитывать изменения в файлах.

Очевидно, что в случае, когда соединение с сервером прерывается, а потом восстанавливается, клиент должен уметь дозаливать файл на него. Есть два параметра, которые необходимо учитывать в таком случае: имя файла и его размер. Но для нас их недостаточно — с хранилищем одновременно могут работать несколько клиентов и файл может обновляться конкурентно. Поэтому понадобилось добавить ещё один параметр.

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

Перед отправкой файла на сервер клиент считает хэш. Дальше он заливает файл методом PUT, сообщая серверу этот хэш в HTTP-заголовке Etag. При получении такого запроса сервер сохраняет размер заливаемого файла и его md5 в специальную таблицу незавершённых заливок. В случае нормальной заливки всего содержимого на сервере вычислется md5 полученного файла и сравнивается с полученным от клиента — при их совпадениях файл принят верно и его можно сохранять.

В случае проблем с соединением — если оно было закрыто или при длительном таймауте — на сервере необходимо было сохранить в таблице реально принятый размер и залогировать в access.log неудачно завершённый запрос. Мы использовали в качестве фреймворка веб-сервера mochiweb и в процессе обработки проблем с обрывами соединений встретились с его особенностями. На любые ошибки библиотека реагировала вызовом 'exit(normal)`, что означает «тихое» завершение процесса. Это нормально, если перед нами стоит nginx для логирования запросов и если при подобном завершении соединения ничего делать не надо. Конечно, можно перехватить такое исключение. Но понять, какая именно из возможных проблем случилась, мы в таком случае можем разве что по наличию известных функций в стектрейсе. Нормальным этот метод не назовёшь, поэтому пришлось править библиотеку на предмет выдачи более вменяемых ошибок.

При обрыве соединения клиент не может полагаться на информацию о том, какое количество байт отправляемого файла реально попало на сервер. Поэтому нам пришлось сделать ещё одну доработку протокола — мы расширили метод HEAD, с помощью которого клиент запрашивает эту информацию, передавая серверу путь куда заливался файл, его размер и md5. Сервер ищет незавершённые закачки пользователя с такими же параметрами и отвечает клиенту, сколько реально закачано. После этого клиент должен возобновить закачку с указанного сервером места с помощью специального запроса — нового расширения метода PUT.

Кроме просто докачки файлов, мы захотели накладывать бинарные патчи файлов — дельта-обновления, — так, как это делается в rsync, но минимизируя нагрузку от этих операций на сервере. Мы разбиваем файл на блоки, по которым считаются быстрые и стойкие сигнатуры. Метод вычисления быстрых сигнатур — rolling checksum — мы позаимствовали из rsync. Сигнатуры блоков используются для поиска совпадающих частей файла, которые пересылать по сети не требуется. Комбинацию из размера блока, сигнатур и md5-файла мы называем дайджестом файла. Для того чтобы клиент смог определить, какие части обновлённого файла ему необходимо скачать или отправить на сервер, ему необходимо получить дайджест файла, хранящегося на сервере. Для этого опять пришлось расширить протокол — на этот раз методом получения дайджеста.

Что же касается самих дайджестов, получаемых с сервера, мы не хотели затормаживать процесс синхронизации их расчётом по запросу, поэтому было решено хранить их на сервере уже посчитанными.

Для начала мы попробовали считать дайджесты во время стриминга файлов в Erlang. Казалось, это уменьшит накладные расходы: порция данных уже лежит в памяти и передать её в модуль расчёта дайджеста выглядело дешёвым решением. К сожалению, из-за специфики работы с памятью в Erlang, это оказалось не так: данные копировались в драйвер, считавший хэши, промежуточные результаты копировались обратно в процесс-обработчик, а затем всё опять отправлялось в драйвер. Это оказалось чересчур ресурсоёмким. Разрабатывать специализиованный драйвер, который хранил бы всё промежуточное состояние внутри и не передавал бы его обратно в Erlang, не хотелось. Альтернативным решением стало складывать файл на диск как обычно, а дайджест считать после полного получения файла отдельной программой, написанной на C и запускаемой из Erlang как порт. Мы использовали этот подход и сократили время на расчёт дайджеста в 10 раз.

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

На самом деле, файлы на Диск загружают зелёные человечки
Для случаев, когда файл обновился на сервере, применяется тот же алгоритм поиска одинаковых частей. Клиенту могут потребоваться и несколько частей одного файла, поэтому мы поддержали запросы с указанием множества Range-ей, когда ответ приходит в виде multipart/byteranges, чтобы снизить количество обращений к метаданным файла.

Ещё один метод, который нужен для синхронизации — получить диффы дерева файлов, чтобы клиент мог определить, какие файлы успели обновиться на сервере. Эта задача отличается от обычного версионирования, поэтому методы, предлагаемые стандартом, нам не подошли, и пришлось ещё раз расширить протокол. Когда клиент хочет обновить файлы, он вызывает этот новый метод, указывая идентификатор имеющейся у него синхронизированной версии. А сервер отвечает идентификатором последней версии и списком изменений, произошедших в файловой структуре (не в самих файлах) с момента последнего обновления. Для этого мы храним историю всех изменений в файловой структуре для каждого пользователя.

Пожалуй, за исключением некоторых мелочей, это всё, чем занимается WebDAV-сервер в Яндекс.Диске. Мы довольны тем, что выбрали именно этот протокол. С одной стороны, он практически «из коробки» отвечал нашим нуждам и не требовал существенных доработок, а с другой — благодаря нему с Яндекс.Диском легко интегрировать многие утилиты и приложения.
Автор: @bedmitry
Яндекс
рейтинг 650,01

Комментарии (51)

  • +14
    Уважаемые читатели, расскажите, что ещё вам интересно узнать о нашем сервисе, и мы обязательно напишем об этом.
    • +3
      Интересно узнать когда заработает для ПДД.
      • 0
        К сожалению, существует техническая сложность с подключением Диска к ПДД аккаунтам. Но мы постараемся решить эту проблему.
    • +12
      Ну раз сами спросили, то:

      Больше гик-прона, пожалуйста! Как масштабируетесь? Как синхронизируете данные между узлами? Как храните файлы? Сколько копий? Как отлаживаете erlang? Как ищите ботлнеки? Как мониторите? Устойчив ли он к неожиданным ошибкам, как говорят? Обновляете на лету или останавливаете сервер? Как тестируете? Есть графики под нагрузочным тестированием?

      Ну и организационные вопросы: Как долго искали erlang разработчика? А если учили, то сколько это заняло? Есть ли у вас стратегия, как обойти дропбокс? :)
      • +4
        Код отлаживаем по логам, никаких dbg и trace. Несколько раз приходилось смотреть в продакшен, чтобы разобраться с утечками памяти.

        Ботлнеков на стороне WebDAV-сервера нет, код очень простой, значительная часть логики перекочевала в другие компоненты.

        Неожиданные ошибки бывают, но они локальны — нисколько не затрагивают другие сессии на той же ноде. Так что да, очень устойчив.

        Обновлять на лету здесь не требуется, так как протокол stateless (в отличие от xmpp), поэтому обновляем с перезапуском сервера.

        Про остальное коротко не написать, так что ждите новых постов.
    • 0
      Будет ли заливка файлов перетаскиванием файла на иконку диска? (как в cloud app)?
  • +1
    как настроить в клиенте яндекс.диска, чтобы на сервис заливались файлы из определенной директории?
  • +2
    почему после установки клиента каждые 10 минут выскакивает сообщение «Прекращена работа программы Yandex Updater (EU)», даже при незапущенном клиенте?
    помогло только полное удаление клиента с компьютера.
    • 0
      Напишите, пожалуйста, в нашу службу техподдержки подробности проблемы — http://feedback2.yandex.ru/disk/. Там вам обязательно помогут.
  • +4
    Планируется ли версионность изменений файлов с возможностью отката? Так как это сделано в дропбокс. 2 версии в сутки устроили бы.
    • 0
      Мы думаем о такой возможности. Спасибо за пожелание! Кстати, почему именно 2 версии в сутки?
      • 0
        В дропбокс бекап делают чуть ли не каждые 5 минут, а если работать с файлом в отслеживаемой директории, то получается миллион файлов с минимальными изменениями. Откатываться на 5 минут назад, не имеет смысла, только на пол дня, или сутки.
        А еще есть совместная работа…

        Это поверхностный взгляд, но возможно чем-то поможет.
        • 0
          Да, спасибо. Это полезный фидбек. Подумаем!
  • 0
    почему mochiweb, а не cowboy?
    • 0
      Об этом мы писали в прошлом посте. Когда мы начинали делать прототип, ковбоя ещё не было.
    • 0
      // мимо
  • 0
    Несколько раз за прошедший год, отказывался работать. Лечилось перестановкой свежего софта. Не знаю может и исправили, но вот ошибка.
    Иконки для общих файлов (открытых) неплохо бы сделать оригинальные, например цветом выделить.
    • 0
      Если снова встретитесь с проблемой в работе программы, перед переустановкой, отправьте нам логи (Меню программу/ Справка/ Сообщить об ошибке) — это поможет нам разобраться в проблеме и быстрее вылечить её.
      • 0
        Постараюсь. Последнее время переписок с Яндексом что-то очень много :(
    • 0
      Кстати да, в принципе никто не мешает еще раз сгенерировать ссылку на файл, но есть один момент, можно нечаянно грохнуть файл к которому дан доступ. Если расшарить файл одноразово то это не страшно, а если разместил ссылку на форуме или еще где либо… И опять же, не вижу какие файлы с доступом а какие нет (паранойя)
  • +8
    Для обновления части файла семантически более правилен метод PATCH tools.ietf.org/html/rfc5789, а не PUT все одно и сервер и клиент ваши, потому у вас развязаны руки.
    • +7
      Да, действительно, этот метод лучше соответствует тому, что на самом деле происходит. Но на момент разработки мы не заметили этот rfc. Если мы будем публиковать механизм таких патчей для сторонних приложений, мы обязательно поддержим его в рамках метода PATCH, чтобы максимально близко следовать стандарту.
  • 0
    Какая ОС стоит на серверах?
    • +7
      Linux конечно же
      • 0
        Ну, в этом в принципе и не сомневался :) Какой дистрибутив-то выбрали? Или взяли голое ядро и окружили своим окружением? :)
  • –1
    WebDAV конечно хорошо, пробовал на проекте в издательстве. Встроеный клиент в MacOS X «из коробки» и Ubuntu работали, а вот в Windows XP со всеми сервис паками и патчами так и нормально не завелась клиентская часть. HTTP range поддержку добавил в серверной части системы.

    Под WinXP нормально монтируется WebDAV диск только с помощью Novell NetDrive.

    WebDAV:
    * Плюсы — открытость, стандартизованность, наличие open source клиентов и серверов.
    * Минусы — поддержка «из коробки» операционными системами, сложность delta протокола.

    Common Internet File System (CIFS) как клиентская часть протокола отлично поддерживается под основными платформами. И создать сетевой диск без специальных клиентов нет никаких проблем.

    CIFS:
    * Плюсы — поддержка основными операционными системами «из коробки» для монтирования сетевого диска
    * Минусы — так и не стала стандартом IETF
  • +4
    Про этот баг я писал очень давно через feedback2.yandex.ru/disk/, но до сих пор не пофиксили.
    Может, тут расскажете подробнее о трудностях? :)



    upd: баг в том, что нельзя удалить папку с эмодзи.
  • 0
    При попытке загрузить/скачать файл более 50Мб падает с ошибкой. Через Web-интерфейс все нормально. Это у меня проблемы или так задумывалось?
    • +2
      Скачать каким клиентом? Можно в личку.
      • +2
        Спасибо за помощь.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Добавил, хотя тут мало рассказывается о языке, больше о протоколе.
      • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Может быть вопрос не в кассу, но тем не менее. Несколько месяцев назад выклюлил Диск из-за того, что клиент стал неконтролируемо клонировать папки. Писал в тех поддержку, не помогло. С тех пор была найдена бага? Или я одинтакой счастливчик?
    • 0
      Попробуйте поставить свежую сборку клиента — http://disk.yandex.ru/download/
  • 0
    Почему был выбран именно erlang? Не самый мейнстриймовый вариант и хотя он простой, но людей готовых на нем писать вроде как не очень много. Тем более, если я не ошибаясь, для jabber-а в Яндексе от erlang отказались.
    • 0
      Потому что отдел, который начал разработку прототипа, занимался jabber-ом Яндекса, который когда-то форкнулся от ejabberd. И мы продолжаем им заниматься.
  • +4
    вопрос не по теме: когда Яндекс позволит покупать больше места?
  • 0
    Мне будет интересно почитать про разработку полезных приложений с использованием API Диска (да и других сервисов яндекса также).
  • 0
    То есть если я захочу залить на яндекс-диск iso мегов эдак в 700, у меня сначала пару минут будет md5 вычислятся?
    • 0
      Зависит конечно же от скорости дисковой подсистемы, но конечно же для 700 МБ это займёт на порядок меньше. У меня md5sum от файла 750МБ считается 4 секунды. Даже в худшем случае трата 20 секунд перед отправкой файла оправдывает возможность возобновить заливку файла, если подключение к серверу разорвётся.

      Конечно же это необязательно, можно отправлять файлы сразу без высчитывания md5, просто докачать будет невозможно, если случится что-нибудь неожиданное.
      • 0
        у меня 40. а разве есть возможность отключить md5?
        • 0
          В нашем синхронизирующем клиенте нельзя. Клиент возлагает дополнительную работу на машину пользователя чтобы минимизировать работу по сети. Кроме того, как в посте отмечалось, это дополнительная гарантия, что файл пришёл правильный и ничего не побилось.

          Если соединение надёжно, можно воспользоваться любым стандартным WebDAV-клиентом, который сможет файл отправить на сервер. К сожалению, не все WebDAV-клиенты умеют работать с большими файлами. Могу порекомендовать cadaver, Cyberduck или даже curl =)
          • 0
            да мне вполне хватает обычного клиента который живет в трее))
  • 0
    Я.Диск не умеет синхронизировать файлы по символьным ссылкам (windows mklink). Менять из-за него расположение файлов отдельных или папок очень не хочется. Планируете ли вы сделать синхронизацию по символьным ссылкам?
    Это единственная причина по которой я не могу перейти с DropBox (давно это умеет) на Я.Диск.
    • 0
      Мы думаем о такой возможности. Спасибо за пожелание! Постараемся учесть это в будущих версиях программы.
  • 0
    Когда будет полноценная синхронизация с устройствами на android?
    Хотя бы выборочных папок. С возможностью выбора карты памяти как места хранения файлов. 32Гб на планшете + WiFi, но Яндекс.Диск — read-only. Загрузить файлы на Яндекс.Диск можно, но ручками, что очень неудобно.
    • 0
      Спасибо за пожелание! Мы пока не думали о полноценной синхронизации с Android-устройствами, но посмотрим, как это можно устроить.
      • 0
        Это надо устроить. Это очень упрощает жизнь конечному пользователю, который не компьютерный гик, а просто юзер, который хочет, например, синхронизировать свой любимый эксельный файл, который он ведет на компьютере, с файлом в котором он вносит информацию, когда объезжает с планшетом свои торговые точки, например.

        Сейчас это сделать непросто
        • 0
          В Dropbox решается очень просто, добавляете файл в Favorites и он начинает синхрозизироваться, хуже когда вам нужна целая папка. Думаю в Yandex диск могут похожий принцип добавить.
  • 0
    Расскажите подобное про Яндекс.Метрику. Лично мне было бы мега интересно узнать

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка