Некоторое время назад, листая бескрайние просторы Хабра, я наткнулся на вакансию «Python Backend Developer».
Больше всего меня привлекло расположение офиса - он был рядом с домом, и я написал ответ. Ответ пришел быстро с вопросом о том, готов ли я выполнить тестовое задание.
Я ответил, что подумаю, если мне его пришлют. Письма с заданием не было две недели.
И вот, накануне майских праздников, пришел ответ в виде тестового задания.
Задача казалась простой, но я решил вообще отказаться от дальнейшего общения, так как почему-то через две недели порыв искать новую работу прошел, а впереди были каникулы.
Однако в тот же день я заболел.
Довольно серьезный насморк со всеми вытекающими.
И на следующий день я решил попробовать выполнить это тестовое задание и посмотреть, что из этого получится.
И именно об этом моя история.
Ниже приведен текст того же задания:
Вам необходимо создать небольшое веб-приложение, используя любой веб-фреймворк (желательно тот, который вы знаете лучше всего).Итак, в качестве веб-фреймворка для Python был выбран Tornado; Я с этим знаком уже давно.Верстка не важна, сосредоточьтесь на бэкенде, дизайне кода и деталях.
По сути приложение должно состоять из одной страницы, но при желании ее можно разделить на несколько (отдельная форма загрузки).
Обязательные элементы — форма загрузки фотографий и таблица со списком загруженных фотографий.
Авторизация не требуется.
Скачать форму: Текстовое поле для ввода названия фотографии Выбор файла Стол: Превью фотографии (необходимо сделать уменьшенную копию фотографии (миниатюру); в этом превью также должна быть ссылка на оригинал/полное изображение, которое открывается по клику на превью) Название фотографии (которое указывает пользователь при загрузке) Производитель и модель камеры (из EXIF, если есть) размер файла Дата создания фотографии (из EXIF) Дата загрузки фото Кнопка «Удалить» Требования: Не сохраняйте существующие фотографии.
Проверьте наличие дубликата файла и при обнаружении выдайте сообщение об ошибке.
Проверьте, является ли загруженный файл изображением; если нет, сгенерируйте ошибку.
(Не используйте проверку наличия данных EXIF в качестве проверки) Не разрешать сохранять фотографии, созданные более года назад (проверьте дату создания фотографии в EXIF).
Если в EXIF нет даты создания фотографии, то следует выдать ошибку и не добавлять файл.
Мы будем поднимать несколько бэкенд-серверов, поэтому нам понадобится балансировщик и Супервизор.
Изначально я думал о HAProxy как о балансировщике, но потом меня осенило, что NGINX умеет хорошо раздавать образы.
В итоге архитектура вначале мне показалась такой: NGINX балансирует соединения и раздает статические данные с диска, 4 сервера Tornado обрабатывают запросы, Redis синхронизирует бэкенд. Бремя анализа входящих изображений и создания миниатюр легло на плечи Tornado. В задании не сказано, какие форматы необходимо поддерживать, поэтому я поискал в Википедии описание EXIF, где упоминаются форматы TIFF и JPEG. Если это все, то дела обстоят не так плохо, библиотека Pillow для Python поддерживает оба формата, а также метаданные EXIF. Но есть нюанс — изображения TIFF не открываются браузером.
Это делает невозможным открытие исходного файла в браузере, поэтому я решил перекодировать эти изображения в JPEG и дополнительно сохранить данные вместе с полученным EXIF-файлом, из которого можно было бы восстановить всю необходимую информацию для отображения в формате.
стол.
Саму таблицу мы сохраним в Redis. И хотя Redis полностью загружается в память и в случае аварийной остановки шансы на восстановление последних изменений базы данных невелики, я считаю, что он способен вместить очень большое количество описаний изображений и прослужит долго.
А в экстренных ситуациях недостающую информацию можно восстановить из метаданных файлов JPEG. Решение с метаданными в JPEG мне показалось красивым, и хотя Pillow вполне способен сохранять EXIF в JPEG, сами метаданные уже должны быть в бинарном формате.
То есть Pillow выдаёт метаданные в виде словаря, но не умеет конвертировать метаданные из словаря.
Была найдена библиотека Gexiv2, которая также работает с метаданными, но ее установка потребовала некоторой сноровки.
Попытки собрать Gexiv2 из исходного кода много раз приводили к ошибкам об отсутствующих библиотеках.
В поисках еще одной такой библиотеки я наткнулся на установочный пакет этой библиотеки для Ubuntu. Но и здесь возникла проблема.
Я установил Python в систему через pyenv и планировал запускать скрипты из virtualenv, но в данном случае установленный в системе Gexiv2 недоступен.
На эту тему есть определенные танцы с бубном, но потратив час на Gexiv2, я решил отказаться от виртуалки и использовать системный Python 2.7.6. Gexiv2 успешно редактирует EXIF в файлах, но у него проблемы с данными в памяти.
И мне принципиально не хотелось обращаться к файлу дважды: один раз, чтобы записать JPEG, второй раз, чтобы записать в один и тот же файл метаданных.
Я еще не знал, что меня ждет. А меня ждало следующее: в документации Gexiv2 перечислены поддерживаемые форматы, такие как EXV, CR2, CRW и многие другие.
Таким образом, Pillow уже не справлялся с задачей чтения загруженных изображений.
Так я нашел ImageMagick и соответствующий адаптер для Python — Wand. Wand выглядел многообещающе — поддержка многих форматов, чтение EXIF, относительно простая установка.
Но для сохранения файлов JPEG с их метаданными мне все равно нужна подушка.
Потратив некоторое время, мне посчастливилось найти библиотеку piexif, которая помогала редактировать метаданные в Pillow, и это стало на одну проблему меньше.
Потратив несколько часов, вы можете сесть и запрограммировать.
Алгоритм был прост, Wand загружает изображение из памяти, выводит данные EXIF, затем Wand отдает буфер RGB, вычисляет свой хэш md5 для проверки на дубликаты, конвертирует буфер в JPEG и сохраняет его со своими метаданными, плюс сохраняет миниатюру.
.
Конечно, мы соответствующим образом обновляем данные в Redis. Остается только проверить.
Однако найти в Интернете картинки с метаданными, да еще и свежие, — проблема.
И я потратил много времени на поиск программы, которая бы хорошо редактировала EXIF-данные.
И вот первый образец JPEG готов, загружаем — работает! Но второй образец, файл CR2 размером 7 МБ, преподнес несколько сюрпризов.
Во-первых, Wand не мог прочитать его из буфера; требовалась подсказка формата в виде расширения исходного файла.
Но тут есть проблема, библиотека начала писать, что не может найти какой-то временный файл.
Поискав еще раз, оказалось, что мне нужно установить утилиту ufraw, и файл прочитался.
За 11 секунд. И тут в JPEG выпало что-то больше похожее на шум, чем на исходное изображение.
Изначально я грешил на Wand, мне казалось, что он криво преобразует изображение в RGB-буфер, однако, когда я запустил калькулятор, я обнаружил, что буфер ровно в 2 раза больше, чем нужно - то есть их не 8 , но 16 бит на канал.
Ура, одна строчка и все работает. Но что делать с длительной загрузкой файлов? Даже если серверов четыре, такое же количество больших файлов CR2 просто сделает сервис недоступным.
В течение первого дня я потратил значительную часть времени на установку системы, поиск и установку различных библиотек, исследование предметной области, но в итоге написал всего 150 строк кода.
И в результате тестовое задание было провалено — результат работы приложения оказался неприемлемым.
И больное тело просило покоя.
На второй день было решено отказаться от обработки изображения во время запроса, загрузить изображение в NGINX, оставить в бэкенде только один сервер, а также запустить три скрипта, которые будут обрабатывать изображения.
Я начал с создания модуля загрузки NGINX, и, конечно же, безуспешно.
Повозившись некоторое время, я понял, что автор забросил его, и на последней версии этот модуль работать не будет. Ну да ладно — пусть сервер Торнадо сохраняет на диск входящие файлы.
Далее, собственно, скопируйте и вставьте в новые сценарии обработки изображений.
Настраиваем Supervisor и NGINX, и завершаем скрипт установки.
И это работает. Из минусов: после загрузки изображения пользователь открывает страницу со статусом загрузки, проходит некоторое время, прежде чем изображение появится в таблице, пришлось отказаться от хеширования чисто RGB-данных, теперь весь файл хешируется для проверки на дубликаты.
В случае ошибки результат загрузки сохраняется в течение одних суток.
В результате я считаю, что при относительно небольшой нагрузке это приложение будет работать стабильно, а на мощном многоядерном сервере можно говорить о хорошей производительности.
К сожалению, это тестовое задание было заполнено в основном поиском библиотек и администрированием, программирования в нем было очень мало.
Что хотел узнать работодатель с помощью этого задания, мне не очень понятно.
Может быть, вам нужно было написать свою библиотеку для получения EXIF-данных? И такое тестовое задание нельзя назвать маленьким — оно заняло более 8 часов.
Было бы значительно упростить задачу, если бы мы включили подробности о поддерживаемых форматах изображений и более подробное объяснение предполагаемого использования приложения.
Исходники можно посмотреть на github .
И я буду продолжать болеть.
Теги: #python #tornado #Nginx #redis #backend #exif #python #Perfect code
Руководитель отдела развития Эквид Василий Васильков написал о том, как кандидаты выполняют тестовые задания и что из этого получается.
Получилось забавно.
Публикуем заметку слово в слово.
*** Расскажу историю одного тестового задания.
Немного длинно, но, надеюсь, интересно.
В Эквиде все тестовые задания для инженеров открыто публикуются на GitHub здесь — github.com/Ecwid/новая-работа .
Вы можете просто начать выполнять любое понравившееся задание, никого не предупреждая, а затем, когда будете довольны результатом, поделиться им со мной.
Одной из первых задач, которые мне пришли в голову, была консольная загрузка файлов по HTTP. Это очень просто и я пришел к этому именно с этой мыслью.
Пусть, рассуждал я, Пока эта простая штука повиснет, а потом, конечно, добавлю крутых задач.
Тогда я что-нибудь придумаю! Я выложу такие крутые тесты, что все ахнут и сразу побегут их делать, а эту примитивную качалку куда-нибудь спрячу и больше никому не покажу.
Почему-то я так думал тогда.
Качельку нужно сделать действительно очень примитивно.
Судите сами — вы даете ему список ссылок в текстовом файле, а он скачивает эти файлы и кладет их в указанную папку на локальном диске.
Должен иметь возможность загружать несколько файлов одновременно (в несколько потоков, например 3 потока) и выдерживать указанное ограничение скорости загрузки, например, 500 килобайт в секунду.
Все.
Для работы с HTTP не нужно ничего изобретать; вы можете использовать любую библиотеку.
Для ограничения скорости - библиотека.
Для загрузки в несколько потоков даже не нужна библиотека — всё есть в стандартной библиотеке Java. Можно, конечно, все это придумать и написать самому на коленях, но это не обязательно.
По сути, вам нужно взять несколько «кубиков» и аккуратно придать им правильную форму, используя немного программирования.
Этому заданию почти три года.
За эти годы я приобрел бесценную коллекцию рокеров всех цветов и размеров.
Если жизнь заставляет меня скачивать что-то в несколько потоков по HTTP, я спокоен как удав, у меня более сотни решений на любой вкус.
Неудивительно, что эту задачу, самую простую и понятную, выполняет большинство кандидатов.
За прошедшие годы этот тест настолько обогатил меня новым опытом, что я уже не могу списывать его в архив, заменяя более крутыми и модными заданиями.
Если сейчас мне пришлют еще 100 консольных загрузчиков, я могу с уверенностью сказать, что именно там не будет работать: — Некоторые из представленных работ просто не будут скомпилированы.
Да, работа будет вестись на скомпилированных языках Java/Kotlin, но она не будет скомпилирована.
Локальная IT-разновидность корпускулярно-волнового дуализма, как я понимаю это явление.
Я, конечно, несколько знаком с языками, на которых прошу вас пройти тест, и без всякой гордости лично выполняю те части, которые кандидат имел в виду, но не написал, оставляя мне возможность для творчества.
Обычно это самые интересные вакансии.
Приятно, что кандидат серьезно относится к своей будущей работе и не только дает вам возможность проверить свои знания, но и, в свою очередь, проверяет ваши.
— Некоторые из представленных работ выдержат скорость гонки в очень широком диапазоне от желаемой.
Например, вы хотите скачивать со скоростью 300Кб/с, а программа скачивает около 2Мб/с.
Разница всего в шесть раз, но строго 2 мегабайта в секунду и не более.
Как сказал один кандидат, «скорость поддерживается в определенном интервале туда и обратно».
Мне очень понравился и «определенный интервал», и «туда-сюда».
Теперь я сам стараюсь как можно чаще использовать эту фразу — «Мы сделаем эту фичу за двадцать дней, пять дней туда-сюда».
— Параллельная загрузка.
С одной стороны, я часто задумываюсь об отмене этого требования, потому что по нему можно угадать возраст кандидата, а мне это не нравится.
Если вам приходит письмо с вопросом «Это как ReGet в нескольких тредахЭ», то вы сразу понимаете, что кандидат вряд ли намного моложе вас и помнит времена модемов.
Еще я помню все эти бесконечные ReGet, FlashGet, Download Master и прочую милую интернет-лажу конца девяностых-начала двухтысячных.
Нет, вам нужно скачивать не один файл в несколько потоков, а просто несколько файлов одновременно.
Однако, с другой стороны, это требование дает неиссякаемый источник удовольствия при проверке задач: * Например, я хочу скачивать в 3 потока, а программа скачивает в 4. Ставлю 5 потоков - программа скачивает в 6. Что за фигня? Как здесь можно сделать ошибку? Захожу в исходный код и нахожу комментарий «Давайте увеличим количество запрашиваемых пользователем потоков на один, чтобы он загружался еще быстрее».
Черт, можешь поспорить.
* Другой пример — кандидат пишет «Программа может загружать файлы в N потоков, но случаи N более одного пока не поддерживаются».
Фраза прекрасна до последней точки.
Читая, я вытирал пот ушами, подивился человеческой ловкости и вспомнил анекдот про математика и камерный оркестр.
Бесконечное количество просто интересных решений от неординарных людей: * Как вам, например, такое решение - задача просто узнавала, файлы какого размера и с какой скоростью следует скачивать, делила размер на скорость и уходила в сон на полученное количество секунд. Я даже где-то в глубине души нахожу это решение логичным - программа должна работать NN секунд, и она работает, до чего вы докопались?! Судя по всему, кандидат не ожидал, что инспектор просто возьмет и увидит (какой обман!), что на самом деле программа накачала.
Действительно, очень умное решение.
* А вот еще один, видимо, очень умный человек, который прислал один класс с пустой основной функцией (и ничем больше) и объяснил: «Вот как я планирую как-то выполнить эту задачу.
Надеюсь, моя точка зрения уже яснаЭ» Я приложил серьезные усилия, чтобы просмотреть этот файл из пяти строк, но, не сумев добиться ясности из файла, попросил кандидата расширить свои мысли шире.
Жду второй год, не теряя надежды, однако.
* Помню замечательного человека, который написал, что скачивание файлов по HTTP — совершенно простая задача и можно ли ему этого не делать? Я в духе ответил, что, конечно, не надо этого делать, и мы расстались друзьями.
Больше я его никогда не слышал и не видел.
Мужчина просто спросил меня в письме, не может ли он что-то сделать; Я, со своей стороны, не нашел в себе сил запретить ему это сделать.
Все.
Надеюсь, мой ответ помог человеку достичь нирваны, а он по-прежнему ничего не делает. * Замечательный кандидат, который сказал, что не знает Java и можно ли выполнить задание на Go? Я сказал в том смысле, что «черт с ним, давайте использовать Go», на что получил ответ — «отлично, я тоже Go не знаю, так что выучу и сделаю»! Скажу честно - я горжусь тем, что знаю (пусть даже случайно) такого настойчивого человека.
Самый интересный случай произошел со мной в Пензе (СЕК, привет!) ночью на улице после after-party. Ночь, улица, людей почти нет, я стою и жду такси, чтобы поехать в отель.
Вдруг откуда-то из темноты ко мне подходит мужчина и говорит: «Василий, я пытался устроиться на работу в Эквид, ты мне отказал и с тех пор я очень хочу встретиться с тобой лично!!» Трудно описать, какие мысли проносились у меня в голове в эти секунды.
Чувак, если ты читаешь эти строки, то спасибо за опыт, ко мне еще никто ночью не подбегал с такими двусмысленными формулировками.
Вообще-то этой несерьезной историей я хотел сказать две серьезные вещи: 1) Уважаемые кандидаты.
Что вас губит, так это ваше нежелание бросить проделанную работу и начать все сначала.
Например, вы начали выполнять задачу, а в конце вдруг обнаружили, что забыли включить многопоточность.
В панике вы начинаете вставлять в свой код костыли concurrency, которые для этого не предназначены.
Вам не обязательно этого делать.
Самый правильный способ — выкинуть все написанное и написать с нуля, учитывая недостающие требования.
Серьезно.
Это не производственный код, костылям здесь не место.
2) Может сложиться впечатление, что я как-то несерьёзно отношусь к кандидатам и их тестовым заданиям.
Это абсолютно неправда.
Каждую представленную работу я проверяю с большим любопытством и удовольствием.
Почти каждое задание, которое я смотрю, дает мне новый опыт; Я узнаю то, чего не знал раньше.
Делайте больше тестовых заданий, делайте их чаще и присылайте мне.
Эквид всегда ищет разработчиков.
Прямо сейчас тоже.
У нас большой НЕ-устаревший проект, географически распределенные кластеры, серьезные нагрузки, умная команда и высокая ответственность.
В общем, это непросто, но интересно.
Основной стек — Java/Kotlin, но очень нужны и специалисты по ReactNative. Ищем разработчиков практически любого уровня от джуниора и выше (Ульяновск, Самара).
Не нужно стесняться, не нужно думать «Подойду ли яЭ», нужно просто выполнить тестовое задание или, если хотите, сначала поговорить со мной, а уже потом проходить тест. Или, может быть, вам не придется проходить тест, в зависимости от того, как вы разговариваете.
Наш стек в виде непонятных слов и аббревиатур — Java, Kotlin, PostgreSQL, Cassandra, Redis, AWS, Consul, Docker, микросервисы.
Ну, есть еще много всего.
По всем вопросам пишите в комментариях или на почту [email protected] Теги: #ecwid #тестовое задание #HR-процесс #java #Управление разработкой #фриланс #Управление продуктом #Карьера в IT-индустрии
-
Загар (Химия Загара)
19 Oct, 24 -
Ad И Его Слабость К Каналу Связи
19 Oct, 24