Okay, so we have our next talk, Elexa, is going to tell us about Rustin Clickhouse, so please
give him a warm welcome.
My presentation today is not about how to rewrite everything in Rust.
It's more about how to not waste years in rewriting, but do something more sane.
It's about our approach in Rust in Clickhouse.
So what is Clickhouse?
It's an open source project, it's pretty big, almost 2,000 contributors, 43,000 stars.
It's a C++ code base, mostly C++, I would say 99%, maybe 95%.
One and a half million lines of code, it exists since 2009, and today it's the most popular
open source on our e-cultate base.
And I can say C++ is not the nicest language, but in 2009 neither was Rust.
So we started with C++, and it was quite a good choice, back then, and it's still
quite popular in databases, data-based management systems, in graphics, applications,
video, like games, computer-rated design, operating systems, drivers, scientific data analysis.
So it still has its place, but the question is, if we start today, should we write Clickhouse
in Rust, not in C++?
Let's take a look.
And the first question is C++, a plain, yes, it is.
In big projects, people try to source this plain, a little, by adding even more plain.
I think many, many ways to test the code base.
So they have segmentation folds, they have data races, and they have to use all types of
sanitizers, other sanitizer, 13-initizer memory, and undefined, however, sanitizers.
They have to use Fizing, and we have to run all types of tests, including randomized
tests, stress tests, performance tests, functional integration tests, compatibility tests,
logic tests, jobs and tests for the correctness of distributed applications.
So on, we run about 10 to 30 million tests in vacations each day.
So it's quite a big pain, but, and you know what, we still have set folds in production.
So there'd be different ways of Rust.
There are quite a lot of reasons for using Rust today.
The obvious one is memory and thread safety, but it is the most obvious.
Another reason is that many modern libraries, many modern projects, are only exist in Rust.
For example, libraries for data lakes, like for iceberg, for long, long time.
The one the library was in Java.
The same was for Delta Lake, but then, for Delta Lake, data bricks implemented Rust library.
But a C++ library, a good C++ library, it still does not exist.
If we don't use either Java or Rust, how can we use Delta Lake?
And another reason is there is a lot of hype around Rust.
I see this full audience, and I would say, yes, this is true.
If this talk was about some boring C++ stuff, probably I will have just a few people in the audience.
Maybe not here, but there are a few arguments about rewriting in Rust.
In the main argument, if we do full rewriting, it will take years.
Make your database, one and a half million lines of code, how to do full rewriting.
We kind of just stop and allocate a year for doing this.
And it will take not a year to take many years.
Another reason is that using Rust is simple, but using C++ is slightly less.
So, but if you use both C++ and Rust, you will get pain from both of the languages.
So, maybe it's not a good choice to use two languages at the same time.
And there is a lot of accumulated knowledge about C++, so why should we throw away this knowledge?
Just to rewrite.
There is too much drama about Rust.
What if we write a database and someone will throw us just because we use unsafe in one file,
and we use unwrap in another file, and people will hate us.
But I want something boring.
I want something sane.
Okay.
By the way, performance and efficiency is not deciding factor.
You can write equally performant code, both in C++ and Rust.
Sometimes it will be easier than Rust, because you can do quicker iterations.
You can optimize quickly sometimes.
You can do it faster in C++ just by avoiding complications with borrowers.
Okay.
So, the approach is not to do full rewrite, but to do iterative development.
To find some library, some small feature that we don't really care about.
And just to test, can we use a library in Rust integrated in C++,
and use it as like a gateway for our Rust development.
If it will succeed, we will add more libraries and more on more libraries,
and maybe we will attract more engineers.
Maybe we will get a relatively rewrite our code.
We will see make build system and see make has some way for integration.
In two thousand twenty-two, it was a library in the name of the corrosion.
So, we selected the library.
Then we found one student just because we did not want our full-time employees to lose sanity.
If we asked them to drop C++ and write in Rust,
and we found one function that we don't really need.
There was a Rust library for Blake 3.
Then we decided why don't add this function to SQL, and try what will happen.
And actually, it succeeded.
We integrated this library and even written an article how Blake 3 in Rust is faster.
By the way, then we replaced it with an implementation from LWM.
That is in C++, so it did not really matter, but.
The point is, this was the third thing we integrated with Rust code.
Here is a pull request from this guy.
And what was the second?
I hope that Rust is good for terminal applications.
Sometimes I think that the only thing that people do in Rust is writing,
and three writes in terminal applications.
So, I decided we have a terminal application.
Clickhouse client.
Why don't improve it with Rust?
There is a nice library for history search.
And we decided to try.
And it was also made by an external developer,
works for Clickhouse, and he writes in C++.
There were some problems like we integrated it, and our terminal application crashed.
Because the library has a panic, and we had to patch this library just to avoid this trash.
Potentially, the usability improved, so it was worth it.
Actually, it was not so easy.
So, you can see the history, we added the library,
then we improved, and we might have built it, then we reverted the library due to a crash,
then we changed the build system, and so on.
But anyway, it worked.
The next step was to entirely new language into Clickhouse.
So, Clickhouse is a SQL database, you write queries in SQL.
But there is an alternative database language, name it,
PRQL, pipeline, and relational query language.
You can see how it looks to the left, we have C++.
Sorry, to the left, we have SQL with the clickhouse dialect
to the right, we have PRQL.
To the honest, I like SQL more.
But anyway, PRQL, it was very fashionable, very happy.
And I decided why don't add it just in case.
Maybe people will prefer to write queries in PRQL.
So, again, we found one student that did not mind,
taking it as a coursework.
And the point is, this is not a small library.
It's a full-like transpider from PRQL to SQL.
And maybe we will find many like details in the build system
that we will have to just integrate integrated library.
And what happened, actually, it wasn't integrated.
No one used PRQL, but anyway.
The next step was to make something practical.
We already integrated three libraries.
And we did not really need them.
Now, something that we actually needed.
A library to support Delta format for data lakes.
What is data lake?
It's why to represent a database in a different integrated form.
Like data format is one thing, query language is another thing.
And you can use different, sorry, query engine is another thing.
And you can use different query engines like clickhouse, data fusion,
data bricks on the same data format.
And there are a few data lake formats,
iceberg and Delta lake.
And I say there were no implementations in C++ on the Java.
And when the first library in Rath the period,
it was published by data bricks.
We decided let's try to integrate it.
And we integrated this library.
And now we have support for Delta lake.
OK.
But while all the integration we face just a bit of problems,
let me go through these problems.
All this wrong is Rath.
It's a so nice programming language.
Everyone loves it.
Rath might be a perfect language.
But the problem is when you integrate,
clickhouse integrate Rath and C++ together.
And the first problem is how to get reproducible builds.
Things like making sure that all dependencies are fixed
and all dependencies are in the source code.
Everything is been applied to supply and chain attacks.
How to avoid things like when the build system
download something from the internet from and Rath the sources.
And it is not easy to solve in C++,
but we solve it at a long time ago.
And in Rath the typically not a problem.
But how to ensure that when you integrate it in Cmic,
it does not download crates.
It enters all the crates.
It was not trivial.
Another problem is when you combine two languages,
you have to write wrappers.
To call the code from Rath in Rath from C++.
You have to figure out the interface,
you have to figure out who allocates memory,
who delegates memory.
And when we try the first time,
immediately our test system found that we did it wrong.
There were crashes and so on.
It was really ever wrong and not really safe.
Fortunately, we already had a phyzen camp
to continue some integration system, so it saved us.
Another problem is how errors are handled in C++ and in Rath.
And in C++ we use exceptions.
Actually, I like exceptions.
Maybe you prepare some, I don't write on tomatoes for me.
I use exceptions.
In Rath people typically don't use exceptions.
You can get something close to exceptions and Rath,
but it will be not easy.
And sometimes instead of other handling people just use panic.
And it is okay for applications that do something like batch processing
for applications that invoke it like once, do the stuff,
and went away.
For several applications, it is quite controversial.
You don't want some third party library to just terminate your server.
So you have to fix all these libraries.
And I would like to say that yes panic is memory safe,
but it is in the same way memory safe as like a borders
as to determine it in C++ or even null pointer reference.
It is memory safe in the same way as panic.
But typically panics are used to indicate some bugs,
fail at the portions.
And when you go seriously with fasting,
you almost certainly will find some corner cases
and will find some uncovered some bugs in Rath libraries.
And in this way, the fact that we write code in C++
and we have to pay for all these testing,
it helps us with Rath as well.
One example is in PRQL,
so immediately we found that if you write a query something like X,
or Y, it will crash.
And we have to fix it.
Not a big problem, but okay.
Another thing is sanitizers.
Maybe you want to say that you don't need sanitizers
since in Rath because it is so safe,
why do you need other sanitizers in Rath?
Why do you need memory?
But we built all our code with sanitizers,
and we want all our builds to continue to be tested with sanitizers.
So all the code must be sanitized.
For memory, sanitizers, it's important that every code that writes
or reads into memory is sanitized.
And initially we had to switch to the nightly toolchain
for us just to get memory sanitizer.
For some reason, we still have some problems with it.
Some Rath libraries are disabled with memory sanitizer,
just because they don't provide some symbols that are required
to compile them this way.
But today it is mostly not a problem.
What about cross compilation?
And again, I can say that cross compilation in Rath
is much better than in C++.
The only problem that again,
we paid a huge amount of effort to make it working with C++.
We had to provide custom toolchames,
custom headers from Lipsy for every system.
But now we have to solve this problem again.
And it was again not easy.
What about dependencies?
For example, we prefer to link everything statically.
We start to link open as a cell.
And we start to link the kernel Rath from Rath.
And it depends on another library name it request.
And request also depends on open as a cell.
And for some reason, now we have two different versions
of open as a cell in the binary.
And actually not just in the binary.
One was statically linked and another was dynamic linked at runtime.
And it broke our hermetic builds.
We found a configuration option just to switch request
to use Rath TLS.
But the problem was that Rath TLS was not tips compliant.
And we had to switch it back to open as a cell.
And I'm sure that it used the same open as a cell.
That is tips compliant.
Okay, problem solved.
What is next?
Composeability of the code.
The question is how what are the conventions that we use for the libraries?
How each library should allocate memory?
How it should spawn threads?
How should maintain connection pools or cashes?
If it does, H.D.s per request, how it managed to retract.
And if you make it one way in our C++ code,
how to ensure that other libraries do it in the same way?
And the answer we cannot ensure it.
Either we just patch this libraries.
Or we get away with different ways of managing these things.
Small surprises.
Like when we edit PRQL, we found that some symbols in the binary.
Now take 50 kilobytes just for the name.
And this is one of the names of these symbols.
I see homesky repeated like 20 times.
The legs are broken.
It's some kind of monomorphization like C++ templates.
I'm not expecting that.
So I would say, no, this is not better than C++ templates.
What about dependencies?
Like software composition analysis.
If we list our C++ libraries that we depend on.
It will be like 20 to my B.30 libraries.
If we list the REST libraries, there will be 156.
Direct dependencies and 6072 interact dependencies.
I would say it's not that bad.
It's not that bad as it is in, say, Node.js.
If we use Node.js, maybe NPM, maybe you will have thousands of dependencies.
Now, 6072.
But it is not as like as boring as in C++.
Where you cannot just add the library.
You have to integrate it into build system.
For this reason, you can have too many libraries.
OK, actually, all problems have been solved.
And now we have just a bit of REST code in C++.
We did not correct it in REST yet.
Maybe there is a chance.
It depends on the enthusiasm of our engineers.
I don't see a lot of enthusiasm by the way.
But there is still a chance.
So, what are the takeaways?
REST is actually a great language.
And you can write in C++ and REST in the same project.
And if you like REST, welcome to be a clickhouse contributor.
Thank you.