Пара слов обо мне. У меня никогда не было серьезного плана делать свой продукт, открывать под это дело компанию, погружаться в custdev и вот это вот всё. Днем я работал в сеньёр девелопером, по вечерам — делал pet-проекты типа онлайн версии настольной игры или онлайн-редактора пиксель-арта, и все было хорошо.

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

В общем, мы разрабатываем PIM-систему catalog.app, и я оказался ответственным за весь процесс, начиная от общения с клиентами и формирования требований и заканчивая оптимизацией SQL запросов. В этой статье я расскажу, как наша система устроена внутри, и постараюсь обосновать, почему были выбраны именно такие подходы и инструменты, как у нас организован процесс разработки.

У нас нет кубернетеса, кликхауса, реакта, бессерверных вычислений, рэббит эмкью, кафки, кибаны, графаны, дженкинса, ноды, эластика, и много чего ещё нет. Зато есть дотнет последней версии, энтити фреймворк, нгинкс и шваггер. Я постараюсь рассказать, как и почему мы дошли до такой жизни, и жизнь ли это.

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

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

Может возникнуть вопрос, а зачем нужна отдельная система, если есть Битрикс или Magento и 1С, и управление всем перечисленным выше можно организовать там? Да, можно, но тут появляются вопросы удобства, доработок, поддержки и обновления всего того, что получилось в итоге. Наверное, не зря PIM выделяются в отдельный класс, и к этому классу Битрикс и 1С не причисляют. На хабре недавно публиковался обзор PIM систем, и я очень рад, что мы в него попали.

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

  1. У меня не было никакого опыта в поиске клиентов, продажах и продвижении. Я всю свою профессиональную жизнь до этого работал с кодом.

  2. Сама по себе задача возникает на том уровне, на котором и решается: условно, в IT отделе компании. Отдавать свою оплачиваемую работу стороннему сервису за деньги есть не так много желающих.

Об этом этапе развития я уже писал на Хабр, там можно найти подробный разбор применяемых подходов и алгоритмов.

Несмотря на это, проект мы решили развивать, и двигаться в сторону полноценной PIM-системы. Это немного исправило положение, полноценную PIM продать проще. Давайте посмотрим, как сейчас она у нас устроена.

Общий обзор архитектуры

В этой статье я не буду рассматривать организацию кода на уровне сборок или классов, речь будет идти про более высокоуровневые вещи: сервисы, веб-приложение, API и так далее.

Все это требуется для работы
Все это требуется для работы

Я выделил девять основных подсистем с точки зрения выполняемых ими задач, и объединил их в пять групп. Какие-то из них могут почти неограниченно масштабироваться горизонтально, какие-то существуют в одном экземпляре, и параллельно работать не могут, но от них, к счастью, и не требуется (у нас никогда не будет миллиарда пользователей, например). У меня получились такие группы:

  1. Взаимодействие с пользователями и API. Их я объединил, по сути это веб-проект, который и страницы генерирует, и API предоставляет. Сюда же включил CDN.

  2. Сервисы по обработке длительных задач. Например, пользователь загрузил файл с миллионом записей для автоматического сопоставления, его обработка займет 5-10 минут. Эти сервисы отвечают за обработку такого типа задач. Могут масштабироваться горизонтально почти без ограничений.

  3. Сбор данных. PIM предназначена для работы с информацией о товарах. Иногда, если система внедрена у производителя, эта информация уникальна, и по сути создается или приводится к упорядоченному виду в PIM системе. В других случаях - не уникальна, она уже есть где-то в интернете. В таком случае мы можем помочь с характеристиками товаров. Для этого мы сделали систему парсинга, к этому моменту она собрала информацию о 73 миллионах товаров с их изображениями и характеристиками.

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

  5. Вспомогательные сервисы. Например, сервис по удалению старых загруженных в систему файлов, поисковый индекс, сервис по хранению файлов.

Взаимодействие с пользователями (интерфейс)

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

Пример того, как выглялит страница товара
Пример того, как выглялит страница товара

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

Отладка тоже максимально упрощается. Весь процесс формирования страницы можно отследить в одном окне шаг за шагом.

Еще из плюсов — любая задача может выполняться одним человеком, у разработчиков нет необходимости согласовывать изменения в структуре базы данных, изменения в API и изменения на фронте, как это происходит в случае специализации каждого члена команды на чем-то одном — базе, сервере или фронте.

Минусы у такого подхода тоже есть, но мы до них не доросли ещё. Они нас настигнут, когда у нас будут тысячи клиентов, десятки тысяч пользователей и команда разработки из 30 человек, а до этого еще довольно далеко.

Для разработки API используется тот же фреймворк. Для документации и интерактивной отладки — swagger. Документация генерируется из комментариев в коде. Кстати, очень рекомендую всем, кто им по какой-то причине еще не пользуется.

Но кроме документации API нужна ещё и база знаний для пользователей. Обычно для этого используют Confluence или другой вики-движок, но мы пошли другим путем, и храним всю документацию в виде Markdown файлов в том же репозитории проекта. Из них довольно просто собирается некое подобие вики: markdown преобразуется в html, добавляется навигация, оглавление и так далее.

Такой подход закрывает несколько важных вопросов:

  1. Документация привязана к коду. Обновился функционал на продакшене — синхронно обновилась и документация.

  2. Доступны все функции гита для просмотра истории и контроля версий.

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

  4. Не нужно администрировать стороннюю систему или сервис.

  5. Из кода просто сослаться на документацию, сделать ссылки на нужные страницы документации на страницах сервиса.

Минусы тоже есть, все же поддерживать большую базу знаний с большим количеством перекрестных ссылок и изображений будет трудоемко и неудобно. Но — это потом, мы еще не столкнулись с такой проблемой.

В этот же блок я поместил балансир и CDN, в нашем случае это сторонние сервисы, настроенные для работы с нашей системой.

Сервисы по обработке длительных задач

Для начала немного контекста: наша система может работать с каталогами в несколько миллионов товаров. При этом часто возникают задачи, затрагивающие много товаров сразу, например, сопоставление записей из загруженного пользователем файла каталогу по нечетким признакам. Файл, при этом, тоже может содержать такое же по порядку количество строк. Алгоритм, который делает такое сопоставление, требует формирования в оперативной памяти разных оптимизированных для этой задачи структур данных. Я об этом когда-то подробно рассказывал.

Пример того, что делает алгоритм сопоставления
Пример того, что делает алгоритм сопоставления

Тут же обрабатываются задачи по формированию больших файлов, по взаимодействию со сторонними сервисами и так далее. Например, эти сервисы должны проверять настроенные почтовые ящики на предмет новых писем, делать рассылки, обновлять данные из API маркетплейсов.

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

С другой стороны, мы хотим уметь масштабировать этот сервис горизонтально, причём, процесс этот должен быть максимально простым. А если какой-то из серверов становится недоступен, то его задачи должны взять на себя другие сервера без какого-либо участия человека в этом процессе. В обоих случаях требуется, чтобы после добавления или выбывания сервера память расходовалась рационально, то есть, поисковые структуры были загружены в память в одном экземпляре. Кроме того, будет здорово, если получится обойтись минимальным пересчетом данных в памяти, то есть, чтобы перераспределение задач между серверами максимально оставляло все “как есть”.

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

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

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

Во-вторых, хорошие и удобные инструменты по профилированию (подписка на DotTrace и DotMemory за примерно 12 долларов в месяц полностью себя окупает). 

В-третьих, возможность использовать небезопасный код в узких местах. Например, нам нужно определенным образом обрабатывать изображения. Первую версию делаем на GDI+, когда понимаем, что алгоритм делает то, что нужно, но медленно — переписываем на небезопасный код и получаем ускорение в десятки раз. 

В четвертых, язык C# очень приятен в использовании. На мой субъективный взгляд.

И еще, .NET позволяет разрабатывать веб-приложение, API и сервисы одинаково удобно, используя один и тот же язык и среду разработки. Для небольшой команды это важно.

Фоновый сервис

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

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

Сбор данных

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

У нас есть сервис, который обходит сайты из нашего списка, для которых мы реализовали парсеры. Загружает страницу за страницей и пытается распарсить нужную нам информацию: наименование товара, его цену, характеристики, картинки. Если что-то на странице подгружается асинхронно, наш сервис тоже подгружает эти данные. Все это происходит без запуска реального браузера, этим занимается наш код с помощью библиотеки AngleSharp (рекомендую).

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

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

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

Эта подсистема хорошо масштабируется горизонтально, стоит просто добавить новый сервер, и он возьмет на себя часть работы. Построено это на крайне простом принципе: каждый из серверов работает независимо, просто регулярно случайным образом берет небольшую порцию работы из очереди. Больше серверов — быстрее движется очередь, и быстрее обновляется информация.

База в этом процессе не является узким местом, работа с ней оптимизирована до простых запросов на вычитку (обычно из одной таблицы за раз с условием по первичному ключу) и batch или bulk операций на обновление и добавление данных.

Хранение данных

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

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

В качестве СУБД тут безальтернативно используется Microsoft SQL Server. Но так было не всегда, вначале вместо него был PostgreSql, но в какой-то момент мы решили сравнить производительность, благо тогда у нас еще не было обилия оптимизированных запросов, и почти все работало через Entity Framework и сгенерированные им запросы. Простое переключение на Microsoft SQL Server ускорило доступ к данным в наших сценариях работы в полтора раза.

Второй аргумент - нам потребовались bulk операции для быстрой вставки большого количества строк за раз. Для .NET был готовый драйвер для MS SQL Server, но не было для PostgreSql. Сделать его самостоятельно — я не уверен, что мы бы осилили. В чем я уверен, так это в том, что потраченное время бы бы никогда не окупили. На самом деле, это самый критичный пункт и переезд мы затеяли именно из-за него.

И третий момент: работать в SQL Management Studio и SQL Profiler было намного приятнее, чем в аналогичных утилитах для PostgreSQL.

В общем, мы взяли и довольно быстро поменяли СУБД, спасибо ORM за это. Кто тут говорил, что так никто не делает, и это сомнительный плюс ORM? Хотя, если честно, тогда мы могли себе это позволить, сейчас — уже нет, слишком много специфичных возможностей используется.

Я не говорю, что PostgreSql плох, возможно, мы не умеем как следует с ним работать.

Файловое хранилище

Операции с файлами спрятаны за интерфейсом, и нам все равно, что будет использоваться для хранения файлов. Сейчас готовы реализации для работы с локальной файловой системой, протоколами WebDAV и SMB/CIFS. В принципе, можно и проприетаренное API интегрировать.

В общем, что в app.config указано, то и будет использоваться. При разработке на локальном компьютере обычно используется локальная файловая система, на продакшене - арендуем файловое хранилище как сервис, работаем с ним по WebDAV и SMB/CIFS. Почему с двумя сразу — не буду вдаваться в делали, но в двух словах, у нас бывают разные сценарии работы с файлами, и где-то лучше справляется один, где-то другой протокол.

Поисковый движок

И про него я уже подробно писал. Его мы разработали сами и оптимизировали именно для наших задач. Сейчас, спустя два года, могу сказать, что подход полностью оправдался, нечеткий поиск по 73 миллионам товаров работает почти мгновенно (десятки миллисекунд), а у сервера аптайм уже, похоже, больше года.

Посмотреть в работе можно без регистрации: мягкая игрушка гусь 160см

Реализация при этом заняла один день, потом было сделано только несколько небольших изменений, на это еще один-два дня в сумме потребовалось.

Интеграции

Это — самая большая боль и проблема. PIM системе жизненно необходимы интеграции с другими системами, и среди них — Ozon и Wildberries. Эти двое отняли от трети до половины всего времени, потраченного на разработку всей системы. Именно благодаря им я научился строить сложносочиненные и сложноподчиненные предложения без единого цензурного слова.

Логирование

Систему логирования я выбирал еще тогда, когда работал над проектом один. У меня в голове было несколько требований к ней:

  1. Автоматическое объединение логов, связанных одним контекстом. В нашем случае это может быть обработка http запроса, работа над длительной задачей, одна итерация работы фонового сервиса.  

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

  3. Скорость работы. Логирование не должно тратить много ресурсов. Сбои в логировании не должны приводить к ухудшению отзывчивости основной системы.

  4. Желательно — библиотека, а не отдельный сервис.

В общем, систему логирования я написал сам. С тех пор нам всего пару раз пришлось там что-то незначительно менять.

Заключение

Я не затронул тему организации процесса разработки и взаимодействия в команде, а также вопросы обновления версий и организации бекапов, об этом расскажу в другой раз.

Труднее всего — понять, что делать и как это продавать. На нашем этапе развития самое важное заключается в том, что технологии должны позволять быстро “пилить фичи” и подстраиваться под рынок. К этому можно подступиться по-разному, у нас в команде мы выработали подход, при котором каждый может сделать любую задачу. 

Для этого нам пришлось максимально упростить процесс разворачивания и отладки проекта. Почти каждый сценарий можно повторить и пройти по шагам, не покидая окно Visual Studio. Поэтому мы не используем микросервисы, брокеры сообщений, шины данных и прочие инфраструктурные зависимости: они мешают отладке, они выбивают из потока, всем нужно держать в голове известные особенности их работы, и разбираться, столкнувшись с неизвестными.

Чтобы начать работать над проектом с нуля на новом компьютере, достаточно поставить SQL Server, забрать из гита последнюю версию кода, собрать проект, зайти в админку и нажать на кнопку для создания нужных проекту баз данных.

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

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

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


  1. dyadyaSerezha
    24.03.2024 04:37
    +5

    1. Зашел на ваш сайт на телефоне, прокрутил пальцем полэкрана, чтобы прочитать второй абзац/предложение, и на него тут же сверху наехала плашка, закрыв большую часть текста. Всё, первое впечатление испорчено. Неужели нельзя сделать самую первую страницу, самые первые секунды нормально?) Потом там ниже появляется текст ну очень большим шрифтом. Зачем? Или ваши потенциальные клиенты заходят только с компа и там всё красиво? (лень проверять самому)

    2. Как человеку далёкому от продаж, мне непонятно, как система сама считает остатки. Откуда она берет эту инфу? Продавцы вводят продажи? Цены она пересчитывает, беря последние цены у конкурентов, наверное, а вот с остатками непонятно. С остатками это вообще получается "складской учёт"?


    1. Razoomnick Автор
      24.03.2024 04:37
      +2

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

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


      1. nin-jin
        24.03.2024 04:37
        +3

        Все-таки, профессиональные дизайнеры делали. 

        Это явно не так. Тут и с визуалом проблемы ("не вкусные" карточки товарав), и с версткой (чего только стоит огромный скролл на полупустой странице), и скрипты ваши на jQuery глючат (например, можно "съесть" гамбургер). И подобных проблем там очень много. Но работает всё очень шустро - это классно.


        1. dyadyaSerezha
          24.03.2024 04:37

          Да, этот скрол с минимумом инфы тоже мне не зашёл.


        1. Razoomnick Автор
          24.03.2024 04:37
          +1

          Спасибо, устраняем проблемы. Часть уже исправили, часть еще в процессе.


      1. dyadyaSerezha
        24.03.2024 04:37
        +1

        Уж лучше эту плашку вообще оставить статически всегда вверху, что ли.


  1. Hamletghost
    24.03.2024 04:37
    +4

    Спасибо за честный рассказ, было интересно.

    Много написано про организацию бд, но ни слова про бэкапы, надеюсь они у вас уже делаются )

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


    1. Razoomnick Автор
      24.03.2024 04:37
      +1

      Хотел оставить подробный рассказ об этом для второй части. Если кратко, то бекапы делаются с самого первого дня в два разных места у разных провайдеров, и доступ к ним имеют разные люди.

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

      Задачи мы делим на небольшие автономные подзадачи, если это возможно. Продакшен обновляется несколько раз в день, поэтому все скрипты в большинстве своем - простые, и до этого протестированные разработчиком и на демо-сервере.

      Когда что-то идет не так, пишем скрипт по откату. К счастью, такого опыта пока реально мало, ни одной базы мы пока что не убили. К несчастью, на этот случай нет четкого плана. Что-то типа "Откатим, если что. Если совсем все плохо будет, будем поднимать бекап".


      1. blood_develop
        24.03.2024 04:37
        +1

        Скрипты можно генерировать автоматически, для этого есть параметр -script для команды update-database. Со стороны разработчика сначала применяем миграцию вперед с этим флагом для создания файла миграции up. Потом откатываем миграцию на предыдущую с этим флагом - получаем файл скрипта отката миграции.

        Если нужно в крипт миграции внедрить скл-код мутации данных, то оный можно написать вызвав метод Sql(string sql) в любом нужном месте как в методе up, так и в методе down.

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


        1. Razoomnick Автор
          24.03.2024 04:37

          Спасибо. Я знал про эту возможность, но почему-то не пользовался. Попробуем на практике.


  1. Dmitri-D
    24.03.2024 04:37

    Обычно это заблуждение про "предсказуемую память" довольно быстро развеивается как только вы начинаете прогонять нагрузочные и пен тесты. Надеюсь вы их провели и убедились что всё в порядке прежде чем делать такие заявления.

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

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

    Добро пожаловать в мир WorldWide или корпоративных проектов, где 1000 одновременно работающих клиентов - просто ничто. Нет, не спорю, можно вертикально масштабировать систему и покупать всё более дорогое железо, чтобы поддерживать растущую нагрузку. А когда вы упретесь в пределы возможностей монолитных решений, то, надеюсь сообразите, что все эти брокеры сообщений и микросервисы придуманы вовсе не для того чтобы затруднить вам отладку. Скорее наоборот - вы можете приписать каждому микросервису контракт и протестировать его на соответствие контракту. Чем хороши микросервисы - вы их можете запустить в любом количестве экземляров - для горизонтального масштабирования. И, кроме того, вы можете работать в команде, где у каждого есть своя доля работы и никто никого локтями не толкает, работая над одним клубком.


    1. nkozhevnikov
      24.03.2024 04:37
      +10

      Но на разработку вы потратили бы в несколько раз меньше времени, не были бы привязаны к платформе

      Не могли бы раскрыть подробнее? Дотнет уже много лет как кроссплатформенный.


      1. Dmitri-D
        24.03.2024 04:37

        Ну нашли к чему докопаться.Т.е. вы согласны с остальным и не возражаете - а там намного больее важные ссоображения чем кроссплатформенность.

        По поводу платформ - хорошо, я объясню. Для вас вся кроссплатформенность сводится к 2м плафтормам - Windows и Linux? А не деле существует масса других платформ, например FreeBSD. Вы собирали .NET под FreeBSD? Наверное нет, потому что совместимые бинарники можно получить только в линукс-эмуляторе. Никаких других способов пока нет / мне не известны / не найдено. Нет способов получить под RT операционными системами, нет для NetBSD, OpenBSD, нет для Solaris и т.п.

        Риск в том, что Микрософт - это коммерческая компания, движимая коммерческими интересами и в любой момент может пересмотреть свое отношение к своим продуктам. Это происходило уже не раз - продукты выпускались и прекращались. Кроме того, иногда коммерческие компании идут в суд и отстаивают свои лицензии, как например Oracle идет в суд против Google/android в отношении Java. Это добавляет риск, а не снижает его.

        Mono в этом смысле выглядит более практичным выбором, но для ее развития нужен консорциум. Это бы придало большую стабильность продукту и позволило бы планировать в терминах LTS и кроссплатформенность в этом случае совершенно натуральная, как мы наблюдаем в случае open jdk.

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


    1. Razoomnick Автор
      24.03.2024 04:37
      +9

      А можете раскрыть чуть подробнее, в чем состоит заблуждение? Можете ссылкой, или сориентируйте, что гуглить. Без сарказма, я хочу лучше разобраться в этом.

      Если что, под "предсказуемой памятью" я подразумевал следующее: нам нужно в памяти иметь дерево на, скажем, 100 миллионов узлов. Тогда мы точно знаем, сколько оно займет памяти, сколько из нее будет потрачено на ссылки, сколько - на непосредственно данные.

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

      Про пентесты - не понял, если честно. Речь про DOS с возможным повреждением памяти и выполнением произвольного кода? Но .NET - виртуальная машина, её задача - такого не допускать, и про такие атаки я не слышал. Мы же не на плюсах пишем.

      Что касается джавы, то соглашусь отчасти. Все-таки .NET для веба лучше подходит по моему субъективному мнению, а C# как язык приятнее. Что касается тайпскрипта и пайтона, то у них преимуществ для решения наших задач я не вижу. Я реально много времени провел в DotTrace, и, боюсь, что в случае с пайтоном и тайпскриптом приемлемой скорости я бы не добился. В общем, сделайте скидку на то, что C# - мой основной язык, а с перечисленными вами у меня гораздо меньше опыта.

      Добро пожаловать в мир WorldWide или корпоративных проектов, где 1000 одновременно работающих клиентов - просто ничто. Нет, не спорю, можно вертикально масштабировать систему и покупать всё более дорогое железо, чтобы поддерживать растущую нагрузку. А когда вы упретесь в пределы возможностей монолитных решений, то, надеюсь сообразите, что все эти брокеры сообщений и микросервисы придуманы вовсе не для того чтобы затруднить вам отладку.

      Я думал, что вся статья об этом и есть. Что сначала нужно найти бизнес-модель, а потом - масштабироваться. Не наоборот. И что с поиском бизнес-модели и места на рынке микросервисы никак не помогают, а сложность вносят и ресурсы тратят.

      Сначала клиенты и продажи, потом микросервисы и шины данных.


      1. Dmitri-D
        24.03.2024 04:37

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

        Печально. Но тогда выши утверждения про предсказуемую память - не основаны ни на чем. Проведите и посмотрите.

        Если что, под "предсказуемой памятью" я подразумевал следующее: нам нужно в памяти иметь дерево на, скажем, 100 миллионов узлов. Тогда мы точно знаем, сколько оно займет памяти,

        Позвольте, но реализация алгоритма на любом языке загрузит фиксированное дерево потратив один и тот же объем памяти. Разница может начаться в зависимости от того как осуществляется освобождение и сколько параллельных запросов вы можете себе позволить на одной ноде. Вы запустите 5 конкурентных запросов и у вас потребность в памяти возратет в 5 раз. Это предсказуемо, но объем никак не фиксированный.

        Про пентесты - не понял, если честно

        Ничего страшного. Не все знают всё. Но почитайте на всякий случай как проводят тестирование https://en.wikipedia.org/wiki/Penetration_test

        Речь про DOS с возможным повреждением памяти и выполнением произвольного кода? Но .NET - виртуальная машина, её задача - такого не допускать

        Ох. Почитайте сколько CVE с RCE в .NET приложениях было открыто. Вот вам просто один пример https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36788 Не делаейте таких bold утверждений.

         Что сначала нужно найти бизнес-модель, а потом - масштабироваться

        Бизнес модель и дизайн системы идут в начале, и дизайн отвечает требованиям бизнес модели и диктует как должно быть построено приложение. Если у вас 1 пользователь и нет и никогда не прдевидится 1000 или больше, то наверное монолитное решение ок. Но если и вдруг масштабирование предвидится в какой-то пусть и отдаленной перспективе, то монолитное решение не годится и нужно заново делать дизайн и заново переписывать почти всё. Поэтому "а потом масштабироваваться" - это ошибка. Дизайн закладывает как вы будете масштабироваться.

        И что с поиском бизнес-модели и места на рынке микросервисы никак не помогают, а сложность вносят

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

        и ресурсы тратят.

        Всё тратит ресурсы. И монолит тоже. Если нагрузки нет, то монолит может дать преимущество. Если есть, то это большой вопрос. С микросервисами вы имеете возможность масштабировать нагруденные участки (сервивисы) и остальное не масштабировать, а с монолитом вы вынуждены масштабировать весь монолит, запуская его целиком на всех нодах, даже если узкое место в какой-то его конкретной части. Пример на пальцах - допустим у вас 1 узкое место. Чтобы работало под нагрузкой вам нужно иметь 10 запушенных экземпляров . 10 запущенных монолиров потребуют больше ресурсов, чем если вы разрежете монолит на, допустим 5 микросервисов, так что то узкое место будет лишь в одном из них и отмастштабируете это 1 место в 10 раз.

        Я реально много времени провел в DotTrace, и, боюсь, что в случае с пайтоном и тайпскриптом приемлемой скорости я бы не добился

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

        Скорость системы зависит от многих факторов - не только от языка. Язык важен лишь если у вас CPU-bound или Memory-bound задача. Если у вас именно такой случай, то да, не спорю, С# может быть хорошим выбором. И да, если вы не хотите тратить время на изучение других языков, то и единственным. А я бы посмотрел в сторону GoLang и Rust в таком случае, оставив пайтон или тайпскрипт для задач по data-moving с минимальными трансформациамяи.


  1. sshikov
    24.03.2024 04:37
    +6

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

    Если у вас не было опыта с такими решениями - ну это тоже решение, работать с тем что умеешь. Особенно в маленькой команде. И у него свои недостатки и ограничения.


    1. Razoomnick Автор
      24.03.2024 04:37

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


      1. sshikov
        24.03.2024 04:37
        +3

        И я не сказал что велосипед хуже. У меня для этого просто нет данных. Я сказал, что велосипед вам тоже будет что-то стоить. Если вы сопоставимую цену просто заплатите позже, иногда даже это уже вполне можно считать плюсом.

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

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


        1. Razoomnick Автор
          24.03.2024 04:37
          +3

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

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

          Просто в контексте стартапа это "приплыли" может и не наступить, и скорее всего - по причинам, которые никак не связаны ни с очередями, ни с микросервисами, ни с фреймворком для SPA.


          1. 0x131315
            24.03.2024 04:37

            Велосипед может жить годами, обрастая новыми фичами и новыми связями. До поры до времени это как бы норм и беды не предвещает. Но именно с этой стороны и будут проблемы позже. Рано или поздно придет время выпилить велосипед, но к тому моменту он будет уже слишком сильно привязан, "врастет" в продукт

            А выпилить велосипед потребуется как минимум по двум причинам: стандартизация и производительность. Общедоступные популярные инструменты уже прошли свой путь развития и доросли до highload, а велосипед придется протащить туда команде на своих плечах, это может быть слишком сложно/дорого. И рано или поздно придет время менять команду, новых разработчиков на поддержку велосипеда будет найти/обучить сложнее/дольше/дороже, чем на поддержку общедоступных популярных инструментов

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

            Это к чему: тоже иногда приходится использовать велосипеды, в надежде скроить/сэкономить, но еще не было случая, чтобы об этом позже не пожалел. Каждый раз в итоге оказывается, что дешевле было потратить еще немножко времени, и разобраться с общедоступными инструментами, и сделать сразу как нужно, по-человечески


            1. Razoomnick Автор
              24.03.2024 04:37

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


              1. nkozhevnikov
                24.03.2024 04:37

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


                1. Razoomnick Автор
                  24.03.2024 04:37

                  С этой подводной лодки я никуда не денусь, это мой проект, и все риски на мне, если что.


      1. ssmaslov
        24.03.2024 04:37
        +2

        А еще бизнес может никогда не вырости до необходимости всего этого. Несколько лет назад была неплохая статья, типа вы не в google и с вероятностью 99% ваша компания не будет такой. Поэтому тащить все наработки гигантов в любой проект это так себе идея. Думаю как раз Ваш подход правильный


  1. Xantorohara
    24.03.2024 04:37
    +4

    В докуберные времена многие системы выглядели примерно так же. Одна-две базовых технологий, классические SQL-базы, файловые хранилища, балансировщик. Всё простое и понятное. До чего же тёплые воспоминания...


  1. stozen
    24.03.2024 04:37
    +1

    Зашёл на сайт с мобилки, нажал два раза на гамбургер - он исчез =)


  1. Atreides07
    24.03.2024 04:37

    Хорошая статья, важно не забывать о том что не надо оверинженирить со старта без необходимости. А можете рассказать подробнее про UI? Что там используется? Razor, Blazor (WASM/SERVER) ?


    1. Razoomnick Автор
      24.03.2024 04:37

      Ничего из перечисленного, олд скул: сервер генерирует html, на клиенте - jquery, пара плагинов, самописные скрипты.


      1. Atreides07
        24.03.2024 04:37
        +1

        Blazor Server и Razor Pages тоже генерирует HTML на сервере.
        Или у вас свое какое то свое самописное решение? Файлики в *.cshtml / *.razor - это то что есть из коробки (razor/blazor)


        1. Razoomnick Автор
          24.03.2024 04:37

          Ой, Razor конечно. Не знаю, как прочитал прошлый ваш комметнарий, и почему Razor не увидел.


  1. nivorbud
    24.03.2024 04:37

    Как я вас понимаю :) У меня тоже сейчас задача обработки сотни миллионов документов... И все правильные "чистые архитектуры", "чистый код" и прочее подобное идут лесом. Я боюсь даже в функции лишний раз код выносить, а не то что нагромождение из иерархии классов делать, ибо всего одна лишняя миллисекунда выльется в часы или даже дни дополнительной обработки.

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


  1. B7W
    24.03.2024 04:37

    День добрый.

    1. А что за провайдер/хостинг такой что дает вам MsSQL с лицензией? Насколько я слышал для России лицензии закрыли.

    2. Из текста непонятно в итоге где храните логи. Поделитесь?


    1. Razoomnick Автор
      24.03.2024 04:37

      Вопрос с хостингом закрывает человек в ЕС.

      Логи хранятся в sql базе данных, поскольку работа с ними производится через Entity Framework, СУБД может быть любой.


  1. hardtop
    24.03.2024 04:37
    +2

    Всё правильно сделали - запустили продукт, сфокусировавшись на самом важном. Да, дизайн устаревший и местами с ошибками - ничего, допилите. В качестве конструктивной критики, про вёрстку https://catalog.app/lite:

    У заголовка H1 на мобилках слишком большой шрифт - вылезает за границы. Для английского языка "Boost your sales" всё помещалось бы в экран. Русские "Управленческий" или "Спецпредложение" часто портят покупные темы с бутстрапа.

    Дальше текст с центрированием. 2 коротких предложения будут смотреться нормально. 2 абзаца текста с рваным левым краем очень неудобны для чтения.

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

    Разрядка текста визуально прыгает в .commun-card H5 - line-height: 1.1 явно маловато.

    Но всё равно - молодцы!


    1. Razoomnick Автор
      24.03.2024 04:37

      Спасибо за замечания, будем исправлять.