Коля и его истории про фулфилмент

Фулфилмент — весь путь вещи, заказанной вами в интернете, до порога вашего дома. Он начинается где-то на далёком складе: вещь нужно найти, собрать и упаковать. Этот этап не всегда простой, он гораздо менее заметен, чем доставка, но не менее интересен. Сейчас я постараюсь вам это доказать.

image-loader.svg

Эффективность процесса зависит от его устройства, но если я сходу попробую объяснить, как устроены склады большого маркетплейса, мой рассказ вызовет много вопросов «Почему так сложно?». Поэтому я начну с основ процесса сборки, а потом постепенно, в несколько шагов, увеличу сложность и покажу, какую задачу решает каждое из усложнений. Мне понравились посты на Хабре про стажёра Васю, поэтому добавлю нового вымышленного персонажа — Колю. Он пройдёт с нами весь путь и настроит процессы на складе. Поехали!

Новый интернет-магазин


Коля с друзьями решили открыть интернет-магазин. Летом они арендовали где-то гараж, поставили там несколько стеллажей и наладили поставки и пополнение полок. Задача Коли — наладить процесс отгрузки заказов со склада для дальнейшей отправки пользователю.

Первые заказы уже начали поступать и их нужно собирать. Коля нанял кладовщика Петю, купил ему ТСД (терминал сбора данных, по сути — смартфон с удобной ручкой и встроенным сканером штрихкодов) и настроил систему так, чтобы тот выдавал Коле по одному заказу из очереди: для каждого заказа — список товаров, которые нужно собрать и упаковать. Как только заказ поступал, Петя шёл собирать его в корзинку. Потом нёс корзинку на специальный стол, где упаковывал заказ в коробку, прикреплял к ней наклейку с адресом и складывал на отдельный стеллаж, откуда курьер забирал готовые коробки вечером. Коля посчитал, что в среднем на обработку одного заказа тратится 5 минут, то есть в час получается 12 заказов. Петин рабочий день длится 8 часов, отнимем час на обед и перекуры — получим 7 часов чистого времени и 84 заказа в день. Это — важное число, предельная производительность системы. Если Колины друзья смогут привлечь больше заказов, склад всё равно отгрузит только это количество, просто потому что больше не успеет. 84 заказа в день умножаем на 5 рабочих дней — всего получается 420 в неделю. Или примерно 1700 заказов в месяц, если считать, что в среднем в месяце чуть больше 20 рабочих дней.

Подробнее про метрики

Зарплата Пети — 55 тысяч рублей в месяц. С учётом налогов расходы на него составляют примерно 72 тысячи. Если поделить 72 тысячи на 1700 заказов в месяц — получается примерно 42 рубля на обработку одного заказа. Это — вторая важная цифра, CPO (cost per order) склада. Ну, вернее, это не CPO, а только зарплатная его часть — ещё нужно добавить все остальные расходы: на упаковку, на аренду гаража, электричество, отопление и т.п., примерно так:

Итого CPO получается (72 000 + 20 000) / 1700 + 10 = 64 рубля. Это полная стоимость отгрузки заказа, которую можно использовать в финансовой отчётности и по которой в моменте понимать, как работает бизнес. Но часто хочется ответить на другой вопрос — насколько вырастут расходы, если заказов станет, скажем, в полтора или в два раза больше?

Для ответа на такого рода вопросы стоимости делят на 2 типа — fixed и variable. Fixed — это то, что в некотором диапазоне не зависит от количества заказов, а variable — то, что будет расти вместе с заказами. Тут интересный момент, потому что зарплата Коли — это на первый взгляд не variable, а вполне себе fixed. И более того, мы делим её на предельную ёмкость склада, что, вообще-то, не совсем правильно: если заказов окажется меньше (из-за сезонности или из-за того, что не смогли привлечь достаточно покупателей), то зарплата останется прежней, а значит, CPO вырастет. Но это всё, на самом деле, эффекты малых чисел, потому что Петя тут один, и нельзя нанять 0,8 или 1,2 человека. Однако, когда персонал начинает исчисляться десятками или сотнями людей, то можно более точно подобрать его численность под потребности, и тогда суммарная зарплата становится действительно variable и напрямую зависит от количества заказов.

А для ответа на вопрос, что будет, если мы привлечём больше заказов, используется метрика variable CPO, которая учитывает только переменные расходы и показывает, сколько нам будет стоить каждый новый дополнительный заказ. Итого, мы для нашего склада имеем следующие числа при полной загрузке:

Понятно, что внутри склада метрик гораздо больше, но эти три позволяют верхнеуровнево взглянуть на процесс в целом.


Компания начинает расти


Всё шло отлично, склад был загружен на 70–80%, Петя успешно справлялся с работой, Коля каждый месяц считал статистику и сохранял в специальной табличке.

Но потом наступила осень и количество заказов начало расти. Склад стал загружен на 100%, каждый день отгружал по 84 заказа. Петя был немного не рад, что теперь приходится работать весь день без простоев, а Коля, наоборот, радовался, что CPO упал до проектных значений, и похвалился друзьям, как эффективно сейчас работает отгрузка. Но тут Колю ждал неприятный сюрприз — коллеги, оказывается, тем, как работает склад, были недовольны: пользователи отказывались делать заказ, когда узнавали, что его доставят только через три дня… Как же это было устроено?

image-loader.svg
Коля заранее продумал, как сделать, чтобы в один прекрасный день склад не завалило: каждый день было доступно 84 слота для заказа, и новые заказы занимали по очереди ближайшие слоты на завтра. После полуночи список заказов на завтра закрывался, и следующие заказы начинали заполнять следующий день, а Коля с Петей уже заранее знали, сколько будет работы и сколько курьеров нужно вызвать.

image-loader.svg

Но осенью заказов стало больше, и ситуация поменялась — теперь доступные на завтра слоты заканчивались раньше, и новые заказы начинали заполнять следующий день, а пользователи видели, что доставка будет дольше обычного:

image-loader.svg

Так продолжалось до тех пор, пока ситуация не стабилизировалась:

image-loader.svg

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

image-loader.svg

Количество доступных слотов удвоилось, доставка снова стала быстрой и количество заказов выросло в среднем до 110 в день — покупатели стали гораздо охотнее оформлять заказ, ожидая более быструю доставку. Но, подумав, Коля заметил ещё одну проблему: если заказ оформляется в пятницу или в выходные, на склад он поступит только в понедельник, а к покупателю — только во вторник. Но никому не нравилось оформлять заказ в пятницу с доставкой во вторник. Коля придумал решение: раз у него теперь 2 кладовщика, он их перевёл на 12-часовые смены по графику »2 через 2»: сначала 2 дня работал один, потом первый отдыхал, и 2 дня трудился второй. Таким образом, склад стал работать без выходных, и Коля выставил ограничение в 132 заказа в день (11 часов эффективного времени смены и 12 заказов в час). Заказов в среднем стало чуть меньше — 90 в день, зато теперь склад работал 7 дней, а не 5, как раньше и суммарно продажи выросли (было 550 в неделю, стало 630), а затраты остались прежними. Крутой результат, но Коля не остановился на этом и подумал, что склад может не закрывать список заказов в полночь, продолжать принимать их в течение дня, а список закрывать только когда заканчивается смена кладовщика и заказы уходят курьерам:

image-loader.svg

Теперь клиенты могли получить заказ уже завтра, а за дополнительную плату заказать доставку за несколько часов в тот же день. Продажи выросли до 100 заказов в день. Коля был очень доволен — оказалось, что, чуть поменяв процессы на складе, можно неплохо повлиять на продажи, ничего не потратив.

Но однажды вечером Коле позвонил кладовщик и сказал, что до конца его смены осталось 2 часа, а в очереди его ждут 40 заказов: он уже не успеет собрать их все. Пришлось звонить клиентам, извиняться и переносить доставку. А Коля пошёл копаться в логах, чтобы понять, как так вышло, что его ограничение не сработало.

Всё сработало — было принято ровно 132 заказа. Проблема оказалась в другом: большая часть этих заказов пришлась на вторую половину дня. Утром склад был недозагружен, а после обеда, когда пришли заказы, время было уже потеряно, и наверстать не получилось. Коля почесал голову и придумал решение — написал скрипт, который каждый час рассчитывал, сколько осталось времени до конца смены и сколько заказов можно успеть собрать за это время, а потом проверял, сколько заказов уже есть в очереди и сколько слотов свободно. Если сумма заказов в очереди и свободных слотов оказывалась больше, чем можно успеть собрать до конца смены, скрипт закрывал часть свободных слотов, не давая принять слишком много заказов. Больше подобных проблем не возникало, Коля какое-то время спал спокойно.

Рост продолжается


Новый склад побольше


Время шло, бизнес постепенно рос. У Коли и его друзей работало уже 4 кладовщика, по 2 в смену. Постепенно пришло понимание: чтобы вместить больше ассортимента и иметь возможность закупать крупные партии по более выгодной цене, нужен склад побольше. Нашли небольшой ангар неподалёку, расставили там стеллажи, начали завозить товар. Коле нужно было перенести операции на новую площадку.

image-loader.svg

Операции, в целом, уже были налажены и их довольно быстро удалось запустить на новом месте. И довольно быстро же выяснилась новая проблема — на старом складе цикл сборки одного заказа длился 5 минут, но новый склад был больше, и на сборку одного заказа стало уходить уже 7 минут, потому что расстояния стали больше, и кладовщику нужно было дольше идти пешком. А 5 и 7 — это большая разница: +40% к variable CPO и +40% к требуемому количеству персонала на то же количество заказов. Коля попросил притормозить переезд, а сам ломал голову, как же обойтись без такого роста затрат. Думал-думал, а потом увидел в одном магазине это:
image-loader.svg
И тут его осенило — кладовщик же может собирать не один, а сразу два заказа. Ему в любом случае нужно пройти по зоне стеллажей: второй заказ можно собрать по пути. Коля быстренько перенастроил терминал, чтобы тот выдавал кладовщику сразу по 2 заказа и про каждый товар говорил, в какую корзинку его класть, купил пару таких тележек, обучил кладовщиков и запустил процесс. Замеры показали, что на такой двойной заказ тратится 9 минут. «Да это даже лучше, чем было!» — обрадовался Коля, перенёс операции на новую площадку и поднял дневной лимит заказов на 10%.

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

Ловушка масштаба


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

Приехал Коля на склад и, помня предыдущий опыт, сразу понял проблему: как ни располагай товары, заказывать их будут в произвольных комбинациях, а значит для сборки нужно будет обойти большую площадь стеллажей. Обойти весь этот склад заняло бы, наверное, целый час времени. Нанимать столько кладовщиков, чтобы каждый по часу ходил с одним заказом, совершенно невозможно — это настолько большие расходы, что никакой бизнес их не потянет. Как же быть? Можно так же давать на сборку по несколько заказов, можно даже не по 2, а по 4 или по 6. Но это не спасёт ситуацию — затраты времени такие большие, что, даже если поделить их на 6 заказов, всё равно получится много. А больше 6 корзин за раз уже не увезёшь, да и сортировка на ходу по 6 корзинам будет неизбежно приводить к ошибкам.

Когда кладовщик собирает заказы, он тратит время и на то, чтобы взять с полки товар, и на то, чтобы дойти до следующей нужной полки. Первое приносит пользу, а вот второе нужно стараться минимизировать. То есть делать так, чтобы расстояние между последовательными полками, с которых кладовщик берёт товар, было как можно меньше.

Когда кладовщик собирает не один, а два заказа, расстояние уменьшается за счёт того, что нужно собрать больше товаров на единицу площади. «Было бы здорово для большого склада пропорционально увеличить количество одновременно собираемых заказов так, чтобы плотность сохранилась», — подумал Коля. Но для этого пришлось бы объединить штук 30 заказов, а столько корзинок один кладовщик просто физически не унесёт, да и ошибок будет немеряно. Можно не носить 30 корзинок, а взять одну большую и рассортировать товары по заказам перед упаковкой, но для одного кладовщика корзинка на 30 заказов — это всё равно слишком большой объём, он столько не унесёт.

И тут Коля вспомнил, как когда-то его учили распределённым вычислениям. Когда массив данных не может поместиться в память одного компьютера, используют кластер, в каждом узле которого хранится и обрабатывается кусочек данных. Самая популярная модель таких вычислений — MapReduce. И Коля решил, что попробует сделать на складе похожую штуку. Объединит заказы в батчи по 30 штук, а собирать каждый батч будет не один кладовщик, а одновременно 8 человек, но каждый только в своей зоне склада — как распределённая операция map. Затем все корзины одного батча, поступившие из разных зон, соберутся в точке, где товары отсортируют обратно по пользовательским заказам и упакуют — это аналог reduce с группировкой по батчам. Таких станций консолидации и упаковки может быть несколько: количество зависит от того, как много заказов мы хотим собирать одновременно. Для сортировки товаров по заказам Коля решил использовать двусторонний стеллаж типа такого:
image-loader.svg
Программа знает, какими были исходные заказы и подсказывает упаковщику, как разложить товары по ячейкам. Затем он переходит на другую сторону и по очереди упаковывает собранные в ячейках заказы.

Новая схема процесса выглядит примерно так:

image-loader.svg

Эту схему часто называют «сборкой волнами», имея в виду, что пользовательские заказы собираются не сплошным потоком, а волнами по 30 штук. Кстати, почему 30? Выбор этого числа — оптимизационная задача. С одной стороны, чем больше, тем лучше, потому что повышается плотность расположения точек, где нужно взять товар с полки, и уменьшаются пробеги на один товар. С другой, одновременно растёт сложность последующей сортировки товаров по заказам и увеличивается общая длительность обработки группы заказов (а клиенты хотят доставку как можно скорее и времени часто нет).

Как устроена оптимизационная задача

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

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

В заказе в среднем 4 товара — это даёт нам 4*x товаров, где x — размер батча. Пусть в батче 30 заказов, а время обхода всего склада — скажем, 1 час, то есть 3600 секунд. Тогда на то, чтобы дойти до одного товара, понадобится в среднем 3600 секунд / 120 товаров = 30 секунд на товар. В общем случае 3600/4х = 900/x. Таким образом, время сборки одного товара — это 10 + 900/x. А время сортировки одного товара почти линейно растёт с повышением количества заказов в батче, потому что увеличивается длина стеллажа, по которому их нужно рассортировать, плюс вместе с ней растут и пробеги пешком. Пусть для примера взять-отсканировать-положить занимает 5 секунд, и по 1 секунде пробега на каждые 5 заказов в батче (один вертикальный ряд стеллажа). Тогда время сортировки получается таким: 5 + x/5.

Попробуем смоделировать разные размеры батча, составим табличку с временем операций на один товар и нарисуем график для наглядности:

image-loader.svg

Минимум времени достигается где-то в районе 60 заказов в батче, но при 30–40 заказах время уже близко к минимуму. Дальше можно думать, насколько мы хотим сократить общее время цикла сборки и сколько готовы за это заплатить. В нашем вымышленном примере 30 — это как раз неплохой выбор.


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

Заключение


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

© Habrahabr.ru