пятница, 18 ноября 2022 г.

[prog.c++] Впечатлился рассказом RisingWave Labs о переписывании cloud database с C++ на Rust

История от стартапа RisingWave Labs: "Building a Cloud Database from Scratch: Why We Moved from C++ to Rust". И дополнение к ней в виде интервью с основателем этого стартапа Yingjun Wu на medium: "The founder of the database rewrite with Rust is back: Was it worth deleting 270,000 lines of C++ code?" (только осторожно, это интервью на medium под звездочкой, поэтому открывать лучше в incognito window, чтобы не нарваться на ограничение по числу бесплатных просмотров).

История хорошая. Если вкратце: некий стартап решил запилить новую cloud database (чтобы это не значило) и начал пилить на C++, т.к. C++ вполне себе естественный выбор, плюс у этих людей был опыт в C++ (а сам Yingjun Wu, как выяснилось в интервью кроме как на C++ больше ни на чем проектов не делал). Пилили-пилили семь месяцев, забабахались вылавливать баги и бороться с проблемами управления зависимостями. И решили все переписать на Rust. Что и сделали. За пару месяцев (да, всего за пару месяцев).

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

Сам я много лет выступал в этих наших интернетиках в качестве адвоката C++, но в данном случае даже не буду пытаться как-то защищать C++. Как по мне, то на основании прочитанного думается, что RisingWave сделали правильный выбор в пользу Rust-а. Наверное, им вообще сразу же нужно было начинать на Rust, а не тратить время на C++.

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

Кому интересно милости прошу под кат.


Итак, сперва моя версия о том, почему же разработка на C++ в RisingWave Labs не взлетела.

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

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

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

Предположу, что речь идет о том, что кто-то применял классическое ООП, кто-то использовал C++ как "улучшенный Си", а кто-то упарывался "modern C++" во все поля.

Однако, если над C++ версией работало всего около десяти человек, то все это выглядит странно. Уж команду из десяти разработчиков вполне можно привести к одному знаменателю. Мне, как предпочитающему авторитарные методы управления, эта ситуация не понятна.

В-третьих, у меня сложилось впечатление, что там изначально была "пятая колонна", которая всегда ратовала за использование Rust-а вместо C++. Просто на начальных этапах их ропот всерьез не воспринимался, а позже, когда все уже наелись багов в C++ном коде, постоянные "а вот на Rust-е бы..." сделали свое дело.

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

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

В общем, мне думается, там C++ная разработка изначально была обречена на неудачу. Меня больше удивляет то, что на C++ писали так долго, чем то, что в итоге от C++ отказались.

При этом все вышесказанное можно однозначно записывать в проблемы C++.

Опыт RisingWave показал, что у Rust-а с этим всем гораздо лучше. Т.е. можно взять тех же людей, которые косячат в C++, дать им в руки уже готовые инструменты, которые считаются де-факто стандартом в Rust-овой экосистеме (тот же tokio) и эти люди выдадут гораздо более качественный результат.


Теперь о вещах, которые вызывают вопросы и, в лучшем случае, недоумение.

Начать можно с того, что непонятно сколько же всего было написано на C++ до того, как было принято решение перейти на Rust.

В блоге указывается что по итогу было выброшено порядка 270KLOC кода. Но не ясно сколько там было кода, написанного именно в RisingWave. Например, в их репозитории можно найти код, который является форком других проектов. Если такое есть в Rust-овой версии, наверняка такое же было и в C++ной.

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

Так что, подозреваю, "родного" кода на C++ там было гораздо меньше. А упоминаемое тут и там число в 270KLOC -- это суммарный объем, включая и сторонние разработки.

Далее меня сильно смущает частое упоминание такой проблемы, как memory leaks. Да, память в C++ может течь, такое случается. Но в современных условиях подобные вещи отлавливаются гораздо проще за счет всяческих санитайзеров и анализаторов. Не говоря уже про дисциплинированное применение умных указателей (которые, начиная с C++11, доступны прямо из коробки).

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

Я бы, скорее, ожидал бы большого количество проблем из категории segmentation fault. Особенно в многопоточном коде, где случаются race conditions. Вот такие проблемы, на самом деле, очень коварные и простыми тестами не отлавливаются. Воспроизводить и охотиться на подобные баги тот еще квест, требующий времени и не гарантирующий быстрых результатов.

Но акцент делается именно на memory leaks (у меня сложилось именно такое впечатление).

В связи с этим у меня вызывает недоумение пример из интервью:

At the same time, I also remembered a bug I encountered in AWS Redshift, where three people kept debugging for two weeks without any solution, and finally found out that it was a memory leak problem, and if the current project continued, we would probably encounter a similar scenario, assuming that the product already had many users at that time, and we still needed to debug for a long time because of this memory leak problem, which was not worth the loss.

Непонятно какое отношение AWS Redshift имеет к разработке внутри RisingWave. Может сам Yingjun Wu когда-то работал над AWS Redshift и экстраполирует свой тогдашний опыт на нынешнюю ситуацию? Или же они в RisingWave как-то работали с AWS Redshift и столкнулись с багом (но где именно: в своем коде? в чужом?)

Короче говоря, для сложного и многопоточного C++ного проекта я бы лично ожидал большого количества других проблем, а не memory leaks.

Вот это вот высказывание меня очень и очень сильно удивляет:

Secondly, package management is very small, C++ has a lot of libraries, and package management is very complicated, you may need to spend hours to figure out how to configure a package management tool in CMake, and even after spending a lot of time, we found that we can’t install it, and we may encounter the problem of renaming (the names of variables used in other projects may overlap with the names in the library we use), all these problems need to be solved manually.

Тут что-то странное. Мне доводилось сталкиваться с ситуациями, когда сторонняя сишная библиотека объявляла макросы, имена которых пересекались с именами макросов в моем коде (вроде LOG_LEVEL или LOG_DEBUG).

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

Так что здесь явно написано что-то странное.

И еще к слову о зависимостях:

The STL library lacks support for some modern programming tools, for example, native co-routine support. As a result, developers must rely on many community projects, and most lack long-term support.

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

А в Rust-е, простите, что-то другое? Неужели все сторонние Rust-овские проекты, которые были задействованы в разработке RisingWave, предоставляют какие-то гарантии?

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

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

Ну и как человеку, который уже не первый год оказывает тот самый "long-term support" забесплатно, мне очень хочется поинтересоваться: а поддержал ли RisingWave Labs финансово те проекты, которые они задействовали у себя?

А на закуску пара перлов из блог-поста про переписывание с C++ на Rust. Номер раз:

C/C++ is undoubtedly one of the most popular programming languages for building database systems.

Я бы понял, если бы было сказано, что "C/C++ are undoubtedly the most popular programming languages", т.е. языки, а не язык. Множественное число должно использоваться, а не единственное, т.к. Си и C++ -- это разные языки. Разные, Карл!

И есть у меня сильное подозрение, что далеко не все в команде RisingWave понимают эту разницу. Отсюда, очевидно, и часть проблем с их разработкой на C++.

Номер два:

Moreover, C++ code can be compiled into assembly language for direct execution on the OS instead of relying on interpreters or language runtime.

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


Теперь несколько банальностей о том, как же вести нормальную разработку на C++, чтобы затем не было мучительно больно.

Во-первых, если у вас есть возможность не использовать C++, то не используйте.

Наверное, это странно слышать от человека, которого C++ кормит и который много защищает C++ от всяческих нападок. Но, тем не менее: сейчас не так уж много ниш, в которых C++ будет оправданным выбором. Поэтому подумайте десять раз прежде чем браться за C++. Возможно, какой-нибудь Go или Rust (или даже C#, или Java/Kotlin) подойдут для вас больше.

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

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

Если у вас такой команды нет, то не беритесь за C++. Либо ищите такую команду на стороне.

Проблемы больших компаний, ведущих серьезную разработку на С++ и имеющих огромные кодовые базы на C++ (вроде Яндекса, Лаборатории Касперского, Google, Microsoft и пр.), я не рассматриваю. Там и без меня все знают.


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

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

4 комментария:

Maxim Shulga (aka MaxBeard12) комментирует...

Не думаешь, что п.5 (про прототип) на самом деле где-то повыше скорее всего? У меня первая мысль вообще именно такая возникла.

Stanislav Mischenko комментирует...

Just because it's cool - железная аргументация, переспорить которую невозможно. C++ официально это не стильно, не модно и не молодёжно. Любая другая аргументация меркнет на этом фоне. Вот этот факт прямо сильно меня бесит. С другой стороны, если есть железобетонное доказательство, что Rust даже будучи в руках неопытного разработчика позволяет оному писать, по-просту говоря, хороший код, то так тому и быть. Но вот как-то не верится. Я далёк от разборок C++ vs Rust, но отдельную статью на тему почему Rust способен даже посредственность сделать звездой программирования, я бы почитал.

P.S. Rust я не знаю, и он мне не нужен, ибо надоело смотреть на то, как кто-то опять пытается изобрести Лисп ;)

eao197 комментирует...

@Maxim Shulga

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

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

Просто момент выбрасывания прототипа убрал дополнительные причины оставаться в C++.

eao197 комментирует...

@Stanislav Mischenko

> Я далёк от разборок C++ vs Rust, но отдельную статью на тему почему Rust способен даже посредственность сделать звездой программирования, я бы почитал.

ИМХО, тут речь не о звездности идет. Тут повторяется история, которую я лет 27 назад наблюдал с приходом Java. Java тогда сделала так, что у людей, у которых на C++ (на Си, на Pascal и т.д.) все регулярно падало и текло, программы стали работать. А если и глючить где-то, то было легко разобраться где и почему (легче, чем в C++, по крайней мере).

Вот тоже самое сейчас и с Rust-ом происходит. Там, где в C++ нужно думать и следить за всем самостоятельно, Rust облегчает жизнь за счет жестких правил и автоматического контроля.

Ну и, очень важно, в Rust-е уже современная экосистема, что так же сильно облегчает жизнь.