Как мы делаем Спортмастер

    Всем привет! Уверен, многие из вас когда-нибудь покупали майку, мяч, кроссовки, ну или какой-нибудь другой спортивный инвентарь в наших магазинах, но мало кто знает, что из себя представляет Спортмастер с технической точки зрения.


    Немного Спортмастера образца 2003 года с сайта web.archive.org

    Меня зовут Дмитрий, я старший java-разработчик в компании Спортмастер, и сегодня я хотел бы рассказать о нашем интернет-магазине, о том, какой путь он проделал, чтобы стать тем, каким вы его знаете сейчас: с чего мы начинали, как развивались, что получилось, а что нет, о проблемах сегодня, и о планах на будущее. Интересно? Добро пожаловать под кат!

    История присутствия нашей компании в паутине начиналась аж в далёком 1999 году с первого сайта Спортмастера, который был просто визитной карточкой и каталогом товаров для оптовых покупателей. Собственно интернет-магазин компании ведёт свою историю с 2001 года. В те времена в компании не было собственной команды разработки online-проектов, и интернет-магазин ухитрился сменить несколько самопальных платформ (теперь уж и не вспомнить сколько). Первое относительно устойчивое решение для нас создал очередной интегратор в 2011 году на PHP, на базе CMS 1С Битрикс. Сайт получился простенький, фактически был использован коробочный функционал Битрикс, с небольшими кастомизациями по оформлению заказа. По железу — стартовая конфигурация включала 2 сервера приложений и один сервер БД.

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

    Какие проблемы мы испытывали на тот момент? Проблем было немало, но самая главная – это нестабильная работа нашего интернет-магазина.

    Мы могли упасть даже от того, что бизнес провёл какую-то рассылку, после которой на сайт пришло ~2000-2500 человек, или, как сейчас помню, рекламный баннер на Яндексе отправил нас в глубокий нокдаун. Конечно, такие вещи недопустимы, ведь это не только упущенная прибыль, но и имидж компании – в общем, мы понимали, что нужно что-то менять. В первую очередь пришло осознание того, что стандартные решения с нашими нагрузками (на тот момент и не сверхбольшими, но все-таки уже и не маленькими) работать не будут. Тогда мы имели ~1000 посетителей онлайн в штатном режиме, ~2500 в пиках, плюс планы по развитию x2 ежегодно.

    Сразу же усилились по железу: добавили ещё 2 сервера приложений и сделали кластер из 2 серверов баз данных. Наш стэк на тот момент — nginx, MySQL, PHP. Параллельно мы пытались оптимизировать текущее решение — искали узкие места, пытались переписать всё, что было возможно. Так как нашим узким местом была база, первой всегда «умирала» она, то мы решили по максимуму её разгрузить. Внедрили sphinx для полнотекстового поиска и вывода товарной плитки с фасетами по выбранным фильтрам и подключили кеши. И вуаля — те нагрузки, которые вчера оказывались для нас фатальными, мы стали с лёгкостью держать.

    Вместе с этим параллельно был запущен пилот, в рамках которого хотели провести технологическое обновление сайта — перевод на принципиально другую платформу. Задумок и идей было много — в то время набирала популярность персонализация всего и вся, персональные рекомендации, рассылки, скидки и прочие полезные штуки, и мы тоже, конечно, хотели всем этим пользоваться. Посмотрели, что есть на рынке из подобного, ну и купили самую дорогую платформу по принципу «Раз дороже — значит, круче». Внедрение планировалось с помощью интегратора, а на нас оставалась поддержка и дальнейшее развитие условно старого ИМ до того момента, пока новый на новой платформе не будет сдан в эксплуатацию.

    Но так как скорость функционального развития текущего сайта была очень высокой, мы решили, что начнём внедрение новой e-commerce платформы с более маленького и простого на тот момент интернет-магазина розничной сети Остин, которая также обслуживалась командой ИТ Спортмастера. В процессе мы поняли, что штука-то здоровенная и функционально навороченная, но с технологической точки зрения устаревшая, а найти людей по её полноценному внедрению оказалось вообще огромной проблемой. Кроме того, сделанный перед началом проекта сайзинг дал сильно заниженные требования к железу и количеству лицензий – жизнь оказалась гораздо более жестокой. В общем, мы поняли одно: Спортмастер мы на ней делать не будем. А так как команда для миграции на платформу была уже в процессе набора, то ребята решили начать прототипирование своего собственного решения, основываясь на требованиях, выставленных бизнесом к новой платформе.

    Технологический стэк был выбран следующий: Java, Spring, Tomcat, ElasticSearch, Hazelcast.

    По итогу, примерно к концу 2014 года, у нас была готова новая версия ИМ, полностью самописная, на которую мы успешно переключились. Она же — первая версия того сайта, который вы видите сегодня. Естественно, сегодняшняя версия гораздо более функциональна и технологична, но базовая платформа у них одинаковая.

    Основные задачи


    Само собой, когда говорим о большом интернет-магазине, речь идет о готовности справляться не только с ежедневными, но и пиковыми нагрузками — быть стабильными для бизнеса и конечных пользователей.

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

    И ладно бы ещё, если бы клиент заказал 15 пар носков по 299 рублей, а в магазине обнаружил, что на самом деле есть только 14 пар и по 300 рублей — с этим можно как-то жить. Смириться, купить, что есть, и жить дальше с этим шрамом в душе. А вот если расхождения по цифрам серьёзные, или ты искал конкретный размер — а его раскупили, пока ты читал отзывы счастливых владельцев клетчатых шорт, вот здесь уже все печальнее. То есть сразу и потеря довольного (до этого момента) клиента, и потеря времени и денег на работу колл-центра, куда этот клиент будет звонить, чтобы узнать, что вообще случилось и почему.

    Следовательно, пользователь всегда должен видеть самую свежую цену и самые актуальные данные о товарных остатках, и поэтому наши кеши умные и знают о том, когда данные в БД меняются. Для кеширования мы используем Hazelcast.

    Кстати, об остатках


    Здесь важно отметить, что сама глубина товарных остатков у нас небольшая. А на самовывоз идет очень большое количество заказов (очень). Поэтому клиент должен нормально забронировать товар в нужном магазине и отслеживать остатки. В свое время, на Битриксе, проблему остатков забороли тем, что просто считали любые остатки более 10 единиц бесконечностью. То есть все, что больше 10, всегда равно 10, а вот уже нижние значения нам интересны для подсчёта и мы их учитываем, подгружаем на сайт.

    Сейчас так делать уже нельзя, поэтому мы загружаем остатки из всех магазинов присутствия раз в 15 минут. А магазинов у нас около 500, плюс ряд региональных складов, плюс несколько торговых сетей. И всё это надо оперативно обновлять. Вишенкой здесь тот факт, что в масштабах РФ очень часто меняются условия работы курьерских компаний, поэтому приходится подгружать ещё и параметры доставки. Плюс ко всему на склады компании идёт непрерывный поток поставок товара, из-за чего количество товаров на складах ожидаемо меняется. Значит, его тоже надо подтягивать заново.

    А вот как формируются идентификаторы товарных позиций (SKU). Есть у нас около 40 000, так называемых, цветомоделей товаров. Если углубляться далее до размеров товаров — получится порядка 200 000 SKU. И по всем этим 200 000 надо обновлять остатки в масштабах 500 магазинов.

    Городов и деревень, в которые мы доставляем товары из магазинов или со складов, у нас тоже десятки тысяч. Поэтому и получается, что вариативность кеша только для одной товарной странички (города * SKU) — это миллионные значения. Подход у нас такой: расчёт доступности той или иной товарной единицы происходит на лету, когда пользователь заходит на карточку товара. Смотрим работу курьерок в регионе пользователя, смотрим их график работы, рассчитываем цепочку доставки и считаем её длительность. Вместе с этим параллельно анализируются остатки в магазинах поблизости, от которых можно организовать подвоз.

    Чтобы было проще со всем этим управляться, у нас есть определённое количество очень быстрых кешей в приложении — благодаря этому мы можем быстро получить по ID все необходимые данные, и на лету их перебирать. То же самое с курьерками — мы группируем их по кластерам, а затем кластера уже сохраняем в базу данных. Раз в 15 минут всё это обновляется, на каждый входящий запрос мы обсчитываем определенный кластер курьерок с нужными параметрами, агрегируем их и быстро выдаем на выходе покупателю — все ОК, вот такие шорты зелёного цвета 50-го размера у нас точно есть, их можно или забрать ручками вот в этих трёх магазинах поблизости прямо сейчас, или заказать в магазин через дорогу (или вообще домой) в течение 3 дней, выбирай.

    Для Москвы такая ситуация может казаться излишней, но вот для регионов это совсем другое дело, там очень часто заказывают товары до какого-то из магазинов (до которого, возможно, тоже надо специально добираться).

    Цифры


    Сейчас сайт получает тысячи запросов в секунду с учетом статики и 500-1000 запросов в секунду к серверам приложений. Количество серверов приложений у нас не изменилось, но вот их конфигурация подросла значительно. За сутки в среднем получается около 3 000 000 просмотров.

    DDoS-ы на сайт временами встречаются. Стучатся при этом ботнетами, причем нашими, родными, из РФ. Давненько были случаи с попытками стучаться ботнетами из Мексики и Тайваня, но сейчас такого уже нет.

    На рынке сейчас есть ряд решений по облачной защите от DDoS, да, и довольно неплохих. Но по определенным политикам безопасности мы не можем использовать облачные решения такого рода.

    Что сейчас


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

    Стек на фронте по понятным причинам за это время не особо изменился — Java, Spring, Tomcat, ElasticSearch, Hazelcast всё так же хороши для наших нужд. Другое дело, что сейчас за сайтом скрывается и множество бэк-офисных систем на различных технологиях. И, конечно же, активно идёт реинжиниринг (потому что запросы к внутренним системам и работу с ними в целом нужно оптимизировать, плюс не забываем о требованиях бизнеса и новых бизнес-функциях).

    А ещё вы можете смело кидать мне в личку (или в комменты) любые пожелания насчет улучшения сайта — как в плане новых функций, так и визуальной составляющей и общего пользовательского опыта. Постараемся на всё оперативно ответить и учесть. А если хотите стать частью команды и пилить всё это изнутри — добро пожаловать.
    Sportmaster Lab
    81,89
    Рассказываем о том, как мы делаем «Спортмастер»
    Поделиться публикацией

    Похожие публикации

    Комментарии 30

      +19
      Спасибо! Отличная рекламная статья ни о чем.
        +1
        Решите проблему с загрузкой сайта! Сайтом пользоваться невозможно, постоянно все висит, тупит, лагает, загрузки страниц долгие. Инет у меня нормальный, пробовал с разных машин, и смартов.
          0
          Пробовали выключить-включить? :-D
            –4
            не умничай, сайт тупит и это факт!
        +8
        Хотелось бы конкретики, а не «рекламного баннера» на Хабре.
        Можно я начну?
        Например:
        1. Почему перешли на Java с PHP?
        2. «DDoS-ы на сайт временами встречаются»
        — какими средствами боретесь? какие сервисы для защиты используете?
        3. «сейчас за сайтом скрывается и множество бэк-офисных систем на различных технологиях»
        — каких технологиях? Какого их применение?
        4. «активно идёт реинжиниринг»
        — какой? что под этим подразумевается? смена технологий? контейнеризация и оркестрация? отказ от Java в пользу более удобных и современных технологий или простой рефакторинг кода?
          +2
          > 1. Почему перешли на Java с PHP?
          наверное тут больше не PHP, а Битрикс виноват. С него переходят, когда подрастут, я думаю не надо говорить, почему.
            +1
            Привет, статья планировалась как вводная, поэтому конкретики действительно мало.
            Отвечу по пунктам:
            1. Проект на PHP достался нам по наследству от подрядчика. И мы его развивали и поддерживали некоторое время собственными силами. В какой-то момент, решили перейти на другую платформу, с функциональной точки зрения более интересную, которая написана на Java. Соответственно, для внедрения, была сформирована команда Java разработчиков. В итоге от стороннего решения отказались и стали писать свое. Да и в целом большая часть ПО в компании на Java, поэтому выбор стека был очевиден.
            2. С точки зрения разработки стараемся увеличить производительность наших приложений, чтобы быть более готовыми к критическим перегрузкам. Стараемся анализировать и фильтровать нелегитимный трафик на промежуточных узлах. Более подробно на данный момент, нет возможности ответить.
            3. Сайт, лишь вершина айсберга, которую видит конечный пользователь. Его функциональность обеспечивает множество систем.
              Система предварительного просмотра и редактирования товаров и категорийного дерева — служит для поиска контентных ошибок в атрибутах товара. То есть, чтобы товар попал на сайт, он должен быть отсмотрен и подтвержден для публикации в этой системе.
              Система расчета уровня сервиса — занимается расчетом стоимости и сроков курьерской доставки в различные населенные пункты в зависимости от состава пользовательской корзины.
              Система для работы операторов колл-центра, которые помогают пользователям с возникшими проблемами — не могут найти нужный товар или оформить заказ.
              И много других систем, отвечающих за синхронизацию товарных остатков, обновление информации о магазинах, обработке заказов и так далее. Мы обязательно расскажем о них более подробно в следующих публикациях.
            4. Ведется большая работа по оптимизации производительности и стабилизации сайта, о ней более подробно обязательно расскажем в будущем. От Java пока отказываться не собираемся, но смотрим и на другие JVM-based языки, у нас есть внутренние проекты на Scala, так же есть планы попробовать Kotlin на некоторых пилотных проектах.
            +5
            Сначала хотели прочитать историю успеха типа. Потом вспомнили что сайт адски тормозит и на бэкэнде и на фронтэнде и что-то расхотелось читать как так получилось.
            Реально, найти какой-то товар по быстрому настроив фильтры — фантастика.
            Сейчас ради интереса первый раз посмотрели на ход загрузки.
            Загрузка главной — 400 запросов, 46 секунд загрузка, 27 секунд рендер ДОМ.
            Переход в какой-то раздел каталога — 300 запросов, 38 секунд загрузка, 17 секунд рендер ДОМ.
            Как так-то вообще?
              +1
              Может сказывается, что ищут только бекендщиков и фулл-стеков (судя по вакансиям)? Нормальной оптимизации во фронтенде там явно не хватает. Плюс куча каких-то сторонних сервисов используется. Может для маркетинга оно и надо, но меру надо знать.
                +1
                Вы правы, в этом направлении нам есть над чем работать. Но где вы взяли такие цифры совершенно непонятно. Можете более подробно рассказать какие инструменты для измерения вы использовали и как эти измерения проводили. У меня иные цифры, на порядок ниже Ваших:
                domInteractive — 850ms
                domContentLoaded — 1049ms
                loadEvent — 2750ms
                  0
                  У меня другие цифры, но тоже не очень красивые.
                  Картинка
                    +1
                    Firefox, последний. webconsole, чистим кэш и открываем главную. Ждем полной загрузки загрузки. Вот прямо сейчас
                    401 requests
                    17.27 MB / 9.13 MB transferred
                    Finish: 49.60 s
                    DOMContentLoaded: 13.87 s
                    load: 20.85 s

                    Независимый тест
                    www.webpagetest.org/result/190621_KQ_3ea85ebbcde89ac79da8473ae66c75d9
                    Start render: 2.8s Last painted: 28.000s First interactive 22.780s
                    Fully loaded: 29.620s 500 requests
                      0
                      Ваши результаты в Firefox не могу прокомментировать, у меня они иные. А вот результат теста примерно похож, будем разбираться, спасибо.
                        0
                        В FF тестировали будучи залогинеными в ЛК, возможно разница из-за этого.
                  +5
                  Вот вы и попались. Давно плююсь на сайт Спортмастера. Он работает криво почти на всех доступных мне ПК. Даже сейчас, проверил на ноуте: наводим мышь на «Каталог товаров» и в выпадающем списке активен только нижний пункт «Все для детей», а остальные пункты не активны, ну как так-то, а? И такая петрушка уже очень давно (ИМХО, так как после очередного захода, поплевавшись, опять давно не заходил).
                    +1
                    Я думаю, что поведение которое вы описываете вполне корректно. Дело в том, что эти пункты меню, которые по Вашему мнению должны быть активны, не являются кликабельными (за исключением последнего пункта), а лишь раскрывают новые категории в правой части меню. Возможно поведение не совсем очевидное, спасибо.
                      0
                      а лишь раскрывают новые категории в правой части меню

                      … при наведении на пункты ничего не происходит. ??? Ну и ладно, пойду на другой сайт…
                    +1
                    Лучше расскажите почему ваш сайт такой тормозной
                      0
                      Так они же написали, почему — чтобы отображать корректные остатки (наличие) товара и где, как и когда его можно получить.
                        +1
                        ааа точно, что это я в самом деле… =)
                        0
                        Давайте может более конкретно, что и где по Вашему тормозит? Какие-то конкретные страницы? Действия?
                          +1
                          тоже решил попробовать, набрал в поиске «палатка», нажал enter, и пару минут мне браузер писал, что читает данные с sportmaster.api.useinsider.com

                          Судя по тому, что useinsider.com про себя пишет — это платформа для магазинов?
                          СУдя по трейсу, вроде недалеко (5 мс), но что же оно там так долго-то делает?

                          Кажетя, простое действие — поискать (причем suggest выдал варианты довольно быстро, хотя тоже хочется оп-быстрее), и отдать результат поиска.

                          В итоге написали «По запросу «палатка» найденo 111 товаров», кажется, что результат в окне браузера должен начать появляться не позднее чем через секунду, для 111 товаров. (да и для 1000 товаров, по идее тоже, не должно такое действие занимать минуты)
                        0
                        Вот такой пользовательский опыт был у меня: заказываю товар на сайте, прохожу все шаги оформления заказа, списываю бонусы для частичной оплаты стоимости товара. Через пол часа приходит смс заказ отменён, товар закончился. Такое не может быть, подумал я, т.к. видел на днях этот товар там в большом количестве. Решил пойти в магазин лично. И действительно товар есть в достаточном количестве. Но как выяснилось уже в магазине это тот же самый товар того же самого производителя за ту же самую цену, но с другим штрих кодом, т.к. другая закупка.

                        Ну ок, беру товар, иду на кассу. Разумеется, рассчитываю на скидку бонусами, а мне кассир говорит на вашей карте нет бонусов. Как нет? А вот так. Спрашиваю куда же они делись? Никакого разумного ответа не получаю. Спрашиваю когда была последнее списание бонусов? называет мне какую-то дату пол года назад ещё в прошлом году. Тогда я уже сам захожу в приложение на смартфоне и вижу бонусов действительно нет, но последнее списание сегодня. И тут до меня доходит, что их не вернули за отменённый заказ. Узнаю почему не вернули бонусы, заказ же отменён, сказали возвращают только через сутки. В итоге их действительно вернули через сутки, но мне нужно было сейчас ими расплатиться.

                        Вывод: за отмененные заказы, которые частично были оплачены бонусами, бонусы нужно возвращать сразу же.
                          +1
                          Я не смогу это как-то прокомментировать к сожалению. Могу лишь предположить, что возможно есть какие-то ограничения на мгновенный возврат бонусов. Спасибо за Ваш комментарий, он дойдет до адресата, не сомневайтесь.
                            +2
                            Ну тогда и я попытаюсь. Заказывал кроссовки. Вовремя не успевал забрать и продлил заказ в первый раз. В последний день, пришел в магазин… а там инвентаризация. Ну да фиг с ним, подумал, что много разных служб внутри большой конторы не могут найти общий язык. Хотя не представляю что сложного в модуле отправки смс, для клиентов с активными заказами.

                            Пришел домой обратно, обнаруживаю что сам продлить заказ не могу, теперь только через колл-центр. Ок, звоню, продлеваю заказ. Вот тут хочу обратить внимание на нюанс. На сайте все корректно, заказ продлен, я вижу последний день когда могу забрать товар.
                            Собственно в этот последний день прихожу в магазин и… моих кроссовок нет. Один из сотрудников сказал, что 3 часа назад продал их из торгового зала. Давай разбираться, как так вышло… Выяснилось, что перед инвентаризацией все заказы отменяются, дабы не устроить пересорт.

                            Естественно ни сотрудники в зале не смогли увидеть корректную информацию по заказу, ни ваша внутренняя система учета остатков, ни сайт доступный для всех.
                          0
                          Если углубляться далее до размеров товаров — получится порядка 200 000 SKU. И по всем этим 200 000 надо обновлять остатки в масштабах 500 магазинов.

                          В 2019 году как-то даже неудобно про такое писать… Лет 15 назад (если не больше) решали (в одной известной тогда торговой сети) подобную задачу (правда магазинов у нас было чуть меньше — порядка 200-250, а SKU чуть больше — порядка 700 тыс.) не особенно напрягаясь и не считали это чем-то выдающимся (скорее само собой разумеющимся).
                            +2
                            Мы не считаем это чем-то выдающимся, просто озвучили примерные цифры.
                            +1
                            Тоже вот недавно пришлось воспользоваться мобильной версией сайта
                            Имеем
                            Процессор Qualcomm Snapdragon 625 MSM8953
                            Количество ядер процессора 8
                            Объем оперативной памяти 4 Г
                            Интернет 4Г 5 палок.
                            Chrome, всего 33 открытых вкладки + новая для Вашего сайта
                            Открытие страницы m.sportmaster — 3 сек
                            Поиск «шорты» клик на «плавательные шорты» — до открытия первых четырех рисунков 7 секунд,
                            клик на модель — 3 сек., какие то подтормаживания при прокрутке вниз.
                            В — общем будучи в магазине и пытаясь понять что мне нужно из того что там есть я «задолбался»
                            повторюсь (Количество ядер процессора 8
                            Объем оперативной памяти 4 Г
                            ), а что быть с более худшими смартфонами?

                            Сайт Вашего конкурента Декатлона мин в 2 раза быстрее.
                              +2
                              Очередная классная статья от Спортмастера про то, как создать себе трудности и потом их героически преодолевать. Примечательно, что некоторые негативные отзывы перекликаются с первой статьей о волшебной бонусной программе. То есть это уже тренд, молодцы. Несколько вопросов:
                              1. Я так понял, что Остин до сих пор с вашей подачки сидит на невнедренном решении или они таки смогли решить вопрос? Навскидку, их сайт как минимум не хуже работает, чем ваш.
                              2. Может вот тут собака зарыта? ---> В первую очередь пришло осознание того, что стандартные решения с нашими нагрузками (на тот момент и не сверхбольшими, но все-таки уже и не маленькими) работать не будут.
                              Не большие же нагрузки. Может, не приди такое осознание в первую очередь, глядишь, не надо было бы перевнедряться N раз и иметь 3 команды внедрения?
                              3. В чем суть статьи, за что хвалить, что лайкать?
                                +1
                                Мы не пытаемся создавать себе трудности. Скорее наш «путь» не без ошибок, как и у всех.
                                По вашим вопросам:
                                1. Остин, на данный момент — это решение на сторонней платформе. Я думаю фраза «работает не хуже» не вполне уместна для сравнения этих проектов.
                                2. Все относительно. Тут дело не только в возрастающих нагрузках, а скорее в сложностях и ограничениях, возникающих при необходимости кастомизации компонентов какой-либо коробочной системы с течением времени. У каждого решения они свои. И в какой-то момент понимаешь, что мы сами сможем сделать лучше, имею ввиду конкретно для нашего проекта, для нашего бизнеса. Такая история у нас была с Битриксом.
                                3. Статья о развитии нашего проекта, о пути, который мы прошли за эти годы, о принятых решениях и ошибках. Лайки — это личное дело каждого.

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

                                Самое читаемое