Как стать автором
Обновить

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

Какой вывод можно сделать?

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

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

Во-вторых, конечно, любой вменяемый разработчик начал бы проектирование архитектуры не с сущностей ORM, а с вопроса «А какие типовые сценарии выборки и манипуляции данными предполагаются?»

А вот уже из типовых запросов и стали бы рождаться схемы данных. А не наоборот, как у вас.
Во-первых, не понятно, каким образом вы сделали вывод, что я забыл о существовании нагрузочного тестирования? )

Во-вторых, разве в начальном диалоге не описано, какие данные и каким образом будут доставаться?

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

Исключение это тяжелая аналитика, но это точно не ваш случай.
Можете подробнее описать, каких глупостей не надо делать?
В защиту оратора выше, нет же никаких подробностей, какая ОРМ, что за запросы. Почему в json сложить данные получилось, а дернуть кастомным select с join'ами не получилось?
Мне кажется, это должно работать в обе стороны на самом деле ) Т.е. подробностей хватает, чтобы назвать меня невменяемым разработчиком, но не хватает, чтобы ответить на мои вопросы? )

Вот подробностей охапка (которая на самом деле не коррелирует с посылом статьи, но я понял, что посыл плохой):
1. ORM — Doctrine. Да, мы проверили все режимы fetch: extra lazy, lazy и eager. Избавиться от лишних запросов они не позволяют
2. Мы пробовали DQL и руками джойнить сущности. Не помогло, т.к. доктрина не использует полученные данные для гидратации и все равно делает дополнительные запросы после этого
3. Мы попробовали прототип на MongoDB с использованием Doctrine ODM — это дало такой же прирост производительности, как и финальное решение (Что кажется логичным). Почему удалось сложить данные в документ? Потому что связи между сущностями образуют дерево (За редкими исключениями, для которых мы оставили связь по внешнему ключу)
4. Почему остановились на JSON столбце? Потому что нужно было перепиливать все аннотации и часть логики (потому что не весь функционал между ORM и ODM одинаковый) + в самом приложении для части сущностей остается ORM + поднимать новую СУБД, опыта сопровождения которой в команде нет. Мы просто запилили надстройку над ORM, которая позволила указать столбец для чтения сериализованной в JSON сущности. С т.з. разработчика ничего не поменялось: он как работал, так и работает с доктриной.
5. Можно было действительно сделать кастомный селект на все 20 таблиц, участвующих в запросе. Но потом эти данные каким-то образом нужно перегнать в объекты. Получить иерархию объектов из строк показалось сложнее, чем из JSON.
6. Ну и я не отрицаю, что были совершены ошибки при выборе технологического стека. И мысль была как раз в том, чтобы поделиться опытом: возможно человек сразу заметит подобный паттерн данных у себя и рассмотрит альтернативы реляционной модели.

В общем для приложения:

Понимать где возможна нагрузка и какая. На пальцах.

Квоты - много и чтения и записи.

Витрины, карточки и тому подобное. Много чтения. Записи нет.

Админка - небольшая нагрузка.

Аналитика - каждый запрос нагрузка любая, на запросов мало.

Не забыть сделать балк операции по загрузке всего.

И соответсвенно не делать глупостей. Вроде квот на sql или упарывания в оптимизацию админки.

БД:

Посчитать количество запросов в бд. Можно тупо логировать все и прогнать все типовые сценарии. Не забыть про списки, скролы и вообще пообщаться с продактами и тестировщикам. Если количество запросов вызывает удивление чинить. Прямо на этапе разработки фич.

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

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

Код:

Просто не делать глупостей. Квадраты не писать, данные фильтровать и джойнить поближе к базе, возможность кешей предусмотреть (делать не надо скорее всего) и тому подобное.

Про типового миддла — не соглашусь. Если взять двоих миддлов и отдать им архитектуру интернет магазина, они сделают работающее решение, но присмотр и контроль их фантазий необходим. А то сменят еще систему хранения на документ-ориентированную.

Присмотр да. Бывают странные идеи. Например, людям захотелось попробовать новенькую технологию.

Но в общем они могут написать сами или почти сами. И это будет работать на 10 рпс.

чего в большинстве случаев вполне достаточно.

1. Когда парням придёт время строить по этому хранилищу сочную аналитику, их уныние будет глубже.
2. Кто в здравом уме развесистую схему данных под ORM выставляет под нагрузку, ну кроме хипстеров с их стремлением к чистому коду. Частый код он в приложении хипстеров, а если его суммировать с АДОМ ОРМ-а результирующая будет грязнее сточной канавы.

<Сарказм>А вообще, стоит позавидовать парням, что их сценарий улёгся в данный профиль хранения. Я б наверно пошёл дальше и хранил вообще сущности в файлах, а индексы в memcashd, и вообще вышвырнул этот сторедж. Кому он нужен такой тормозной</Сарказм>. И да, в шутке про файлы только доля шутки. Принять имя файла за ключ, а содержимое за значение, подобрать вменяемую ФС и вложенность директорий, и может неплохо получится. А главное этот стор можно разместить много где и жрёт он не так существенно. Есть много проблем, но всё-таки… Ну это так, мысли вслух.
Не очень понял, вы предлагаете выкинуть ORM-ку из приложения полностью или для участков кода, к которым высокие требования в производительности?
Какие, по вашему мнению, проблемы возникнут при построении аналитики?
На горячем коде ИМХО нужно контролировать запросы и точно понимать, что идёт в БД, чтобы самому или с помощью товарищей суметь покрыться слоем живительных индексов. ORM может творить с запросами чёрное и даже очень искушённые товарищи не смогут дать ума этому монстру.

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

Просто мы пробовали в действительности на нескольких побочных сервисах разрабатывать без ORM, но это показалось достаточно тяжелым для поддержки

У нас на самом деле перелапачена ORM'ка, чтобы позволить читать из JSON столбцов данные и достаточно быстро гидрировать иерархию сущностей, но вместе с тем нормализованная схема так же была сохранена (этакое разделение на БД чтения и записи).
БД в моём комментарии, это удобное представление данных для всяких инструментов бизнес аналитики, ну или просто подцепить zeppelin и поиграться с графиками поверх запросов с агрегирующими функциями. Если ничего подобного не предполагается, либо анализ чего бы то ни было будет осуществляется всякими нейроночками, map-reduce и прочей магией, то да, отсутствие БД никто не заметит. Однако я старая больная обезьяна, и в этом месте хотелось бы иметь право на SQL запрос.

ORM сокращает время разработки и делает код понятным не знакомым с SQL людям. Однако, на всех конференциях и во всех видео, базёры, при упоминании ORM-а, начинают страшно меняться в лице и изображать лютую душевную боль. Я сам смотрел на поведение PostgreSQL с человеческими и с ORM запросами. У меня волосы шевелились на всех местах, ибо где было достаточно 1-го запроса, эта гадина пускала в базу 10. Возможно я не умею готовить Алхимию, но что-то мне подсказывает что проблема глубже. ORM вполне может стать проблемой при росте, но это моё субъективное ИМХО ORM-о ненавистника.
Согласен ) Признаться, я тоже недолюбливаю ORM (по крайней мере, ту, которая досталась по наследству)… и DBA наш не любит, и ведущие разработчики до меня не любили… Но альтернатива непонятна, поэтому приходится мириться с недостатками и костылить.
ОРМ хорош для ненагруженных небольших сервисов. Скорость разработки идет в плюс, а минусов в таком сценарии особо и нет.

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

Это вообще не шутка, а одна из распространённых схем хранения (см. "БД" git vs БД fossil). Я серьёзно рассматривал файлы vs СУБД в качестве системы хранения. Когда-то мне даже настоятельно рекомендовали использовать файлы (но я не поддался). СУБД подкупают принципами ACID. На сайте sqlite даже есть какое-то сравнение её с файлами.

Чтение сериализованного объекта из СУБД скорее всего будет работать быстрее чтения с файловой системы.

Ну и кроме ACID в СУБД есть индексы.

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

1) nodejs при запуске приложения сканирует все файлы и составляет поисковый индекс в памяти процесса (долгий старт даже на 4 тыс. файлах).

2) скрипт поиска не умел в перестроение индекса, поэтому при импорте новых ресурсов надо перезагрузить всю программу (но это баг конкретного поисковика на JS)

3) есть ограничения на длину имени файла на файловой системе

4) столкнулся с проблемой, что некоторые файлы, созданные nodejs, нельзя потом удалить через проводник Windows или через консоль (в чём проблема так и не разобрался)

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

В случае использования какого-нибудь stateless языка типа PHP, использование множества файлов в качестве БД совсем плохой идеей кажется: ни поиска ни индексов на файлах как-то не хочется делать, а если для поиска и индекса БД использовать, то и данные логично там же хранить.

"Файлы" это только интерфейс. Они же не прячут фундаментальные проблемы целостности и доступности. Либо у вас простая файловая система на простом диске (и тогда в случае отказа вы теряете данные), либо вы решаете все те же проблемы с репликацией, фейловером, кворумом, как и с базой данных. Nosql это по сути и есть такая распределённая фс, только без posix-слоя.

а потом понадобилось изменить имя атрибута у всех сущностей..

Ну да, согласен )

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

Обновлять сущности можно асинхронно, если бизнес логика это позволяет.

Скрипт?

У меня был подобный опыт, и даже несколько раз.


1) Задача была в хранении и достаточно быстрой обработке довольно больших пространственных моделей (объём данных — сотни мегабайт-десятки гигабайт на 1 модель, требуемое время извлечения-обработки-записи — секунды-десятки секунд). От предшественников мне досталась реализация на РСУБД. После того, как я переписал всё реализовал хранилище в виде блобов в sqlite с небольшими ухищрениями, скорость возросла на 2 порядка. Да, при прошлой реализации (с сетевой РСУБД) предполагалось, что расчёты будет вести умный, быстрый, оптимизированный сервер СУБД. Предполагалось, что сервер СУБД справится. Предполагалось, что понимание сервером схемы БД решит все проблемы. Но за нас эти проблемы так никто не решил, тогда как хранение данных в виде блобов в sqlite (я выделяю эту фразу как некое надругательство и над РСУБД с их детальными схемами, и над NoSQL-СУБД, т.к. блобы хранились в обычных РСУБД) оказалось в нашем применении идеальным (требуемой в нашем случае "аналитике" наша схема хранения только помогает, хотя "аналитика" вся, естественно, на клиенте).
2) Требовалось заменить структурированные текстовые файлы конфигурации на что-то более надёжное и быстрое. Был выбран тот же sqlite. В итоге fail — производительность оказалось плохой в сравнении с файлами. Не смогли (хотя Google, как я понимаю, в Android что-то такое смог).


Выводы? (в моих случаях)
1) Производительность важна. Правило "если производительности не хватит, купим более мощный сервер" — не всегда работает. (хотя, позже я видел, что некоторые из встреченных мной проблем производительности в принципе могли быть заткнуты достаточно большим кластером)
2) Понимание разработчиком всех деталей работы системы важно. Скорость отклика компонентов системы и количество из вызовов — как раз в числе этих деталей, хотя и отличие SQL и NoSQL тоже. В моём случае (1) разработчикам требовалось понимать, что сервер РСУБД никто не оптимизировал под наши конские требования, а "аналитика" настолько сложна, что её можно реализовать нормальным образом только на клиенте. Понимание этого пришло позже.

10 запросов в секунду и median 700+ms? Что-то я вам не верю. Либо вы странно сделали, либо написано оно у вас на интерпретаторе (бейсика). Гугль подсказывает, что постгря на умеренных nvme/ssd (класса "лучше чем в ноутбуке") выдаёт 10к+ tps с latency < 4ms.

В статье действительно непрозрачно,

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

Но, например, до последних оптимизаций из-за связей М: М, которые не укладывались в документ, было порядка 300 запросов к СУБД при 1 запросе к API.

10*300 — т.е. 3000 tps, в три раза меньше того, что я видел в интернетах. Возможно, у вас особые запросы, конечно (а бывают не особые?), но в целом, мне статья выглядит очень странной. Либо много недосказано, либо "не справились". Алсо, если запросов 300, то проблема не в скорости БД, а в сериализации. 300*4 = 1200 ms, так что ваши 700 ms/300 = 2ms на запрос в БД.


Дальше мы уходим из БД в мир администрирования и начинаем любоваться RTT, думать о разнице в latency виртуальных машин и бареметал, mlfa, cut-though switches и вариантах congestion algorithm для tcp. (ну и о том, что у 1G и 10G радикально разная latency).


На самом деле причина в одном — 300 последовательных запросов.

Дак я вроде в статье и не кидал камней в огород СУБД и сразу отметил, что причина такого быстродействия — огромное количество запросов, порождаемое ORM (из-за сильной нормализации и большого количества связанных сущностей), либо я не понял вашей претензии )

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

Дело не в количестве запросов, а в том, что они (судя по всему) выполнялись последовательно. Запросили одни данные, дождались ответа, потом вторые, потом третьи и т.д. Правильно — connection pool и сколько запросов в параллель, сколько предметная область умеет. А лучше не много запросов, а аккуратный join.


Я это несколько раз видел у клиентов, переползающих от схемы "база данных на 2 юнита ниже сервера приложения" в всякие cloud'ы. Делали много-много запросов один за одним, раньше latency была 0.05ms, стала 0.1ms — и всё, двухкратное падение производительности no matter what.

Зачем так сложно! Пулы соединений, работа с latency дисков, итд. Проще запросы переписать под left join, и будет всем счастье! И база на одном запросе будет летать, и сервер не нужно дорого прокачивать. Ещё приправить все индексами с кешированием и полетит в космос!

Недавно тоже пригласили посмотреть. "Все тормозит". Ситуация: есть MSSQL. Нужных индексов нет, вообще. Зато есть какие то монстры из 10 колонок, видимо от index tuning advisor.

"Посмотрите, мы упёрлись в производительность MSSQL и нам говорят что надо переходить на NoSQL"

300 запросов к СУБД при 1 запросе к API

Что ж я делал не так, когда на БД с несколькими десятками таблиц, кучей связей разного типа и ORM получал 1-3 запросов к БД на один запрос к API? И да, со сложными фильтрами, сортировками и пагинацией. И на локальной машине один запрос к API тратил меньше секунды? Что же я забыл?...

А, вспомнил что-то... Ну там, типа, анализ сценариев использования, индексы разные под разные сценарии, view для наиболее сложных мест, аккуратное использование ORM, чтобы она осиливала всё, что нужно, уложить в один внятный запрос, от которого не бросает в дрожь, ну и всякое прочее по мелочи....

Нет, реально, что это за ORM, которая каждый раз в базу лезет как в лабиринт? 300 запросов, да ещё в структуре из 6 таблиц?? Я даже не могу представить, как это...

Нет, реально, что это за ORM, которая каждый раз в базу лезет как в лабиринт? Да ещё в структуре из 6 таблиц??

Сделать так что вместо джойна генерится N запросов к связанной таблице можно легко на любом ORM.
Очевидно, что в статье я привел минимальную схему (выкинув все костыли конкретной компании и не очень релевантные таблицы). В реальности у нас схема из 120 таблиц.

У нас сейчас всего порядка 5 запросов к БД на 1 запрос к API. Я на самом деле рад, что вы можете так написать приложение с первого раза и что потом не приходится оптимизировать )

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

Корнем проблемы являлось большое (очень) количество запросов к БД - на каждое отношение между сущностями ORM генерировала дополнительный запрос.

Пускай общество знает своих героев! Я просто не встречал пока что таких ORM, если, конечно, с lazy load не заигрываться

НЛО прилетело и опубликовало эту надпись здесь
Не могли бы вы объяснить, каким образом eager_loading помог бы в данном случае?

Если посмотреть на ту же имплементацию в Doctrine: все отличие в том, когда будут сделаны эти самые доп. запросы — по мере обращения к свойствам класса или при загрузке самой сущности.
НЛО прилетело и опубликовало эту надпись здесь
О каких вменяемых ORM вы говорите?
Если взять ту же доктрину:
1) Начнем с того, что при использовании Inheritance = JOINED EAGER просто не работает (потому что не реализован для персистера)
2) Продолжим тем, что EAGER для M:M не будет работать при использовании лимитов на выборке
3) Далее, если связанная сущность использует Inheritance, то EAGER так же не будет работать
4) Ну и закончим тем, что доктрина джойнит только связанные с этой сущностью, но не их соседей. Для соседних сущностей она делает дополнительные запросы после гидратации первых.

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

Рискую показаться излишни резким, но если разработчики не знают как использовать LEFT JOIN, то им не стоит доверять не только MySQL, но и Монгу, но и бэкенд в принципе. А на всякий случай и фронтенд / мобильную разработку.
Корнем проблемы являлось большое (очень) количество запросов к БД — на каждое отношение между сущностями ORM генерировала дополнительный запрос.


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

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

Вы где-то накосячили и закрыли дырку костылём)

Даже ORM в Yii2 разберёт такую структуру данных без проблем. (говорю как бывший разработчик аналитической платформы по новостройкам России) Там были "пожесче" связи и API с фильтрами + 15 джойнов. Percona 5.7 переваривала до 300мс, Postgresql до 200мс.

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

Вы забыли написать объём данных, профиль нагрузки и самые частые операции, которые надо уметь делать.

10 гигабайт данных и преобладающая нагрузка на чтение - это одно решение. 1 терабайт и преобладающая запись - совсем-совсем другое.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории