Thank you very much for attending my lecture. 非常感谢大家听我讲座。
I'm very happy to be with you. 我很高兴和你在一起。
And today I will talk about JSONB performance. 今天我将谈谈JSONB的性能。
This slide are available already. 这张幻灯片已经有售了。
This joint talk with my colleague Nikita Glukhov. 这是我与同事尼基塔·格卢霍夫的联合讲座。
This is a picture of an elephant with projects I was working on. 这是一张大象和我正在做的项目的照片。
Maybe you know some of them. 也许你认识其中一些。
I'm research scientist at Moscow university and most interesting to me is I'm major Postgres contributor. 我是莫斯科大学的研究科学家，最有趣的是我是Postgres的主要贡献者。
And my colleague Nikita Glukhov he's working also in Postgres Professional company. 我的同事尼基塔·格卢霍夫也在Postgres Professional公司工作。
He's also Postgres contributor and for several years he did several big projects. 他也是Postgres的撰稿人，多年来参与了多个大型项目。
So we today will talk about the JSON performance. 所以今天我们要谈谈JSON的性能。
And the reason why I decided to talk about this here, we know that JSON is, as we said, 我之所以决定在这里讨论这个，是因为我们知道JSON正如我们所说，
one type fits all. 一种类型适用于所有人。
You know that modern architecture is micro series architecture and JSON is very good for this architecture because client applications, front end, back end and now database, they use JSON. 你知道现代架构是微系列架构，而JSON非常适合这种架构，因为客户端应用、前端、后端，现在还有数据库，都用JSON。
It is very, very easy for start up to start their project. 创业公司启动项目非常非常容易。
You don't need the relational scheme, how to make it proper, how to make it it's very difficult when you start your project, when you start your business to predict what the schema will be. 你不需要关系方案，如何让它变得恰当，当你开始项目、创业时，预测模式会是什么非常困难。
In the several months. 几个月来，
With JSON you don't have any problem. 用JSON就没问题。
Just have JSON. 只要有 JSON 就行。
And all server side languages support JSON SQL JSON so it's not something different from SQL. 而且所有服务器端语言都支持 JSON、SQL、JSON，所以这和 SQL 没什么区别。
What's very important that JSON relaxed object relational mismatch. 非常重要的是，JSON 放松了对象关系的不匹配。
In the code you work with object, in relational database you work with relations. 在代码中你处理对象，在关系数据库中你处理关系。
And you have some contradictions between the programmers and between d/b/a. 而且程序员之间和d/b/a之间存在一些矛盾。
But when you hear use JSON, there's no any contradiction. 但当你听到使用JSON时，就没有矛盾。
So this way JSON becomes very, very popular. 这样JSON才变得非常非常受欢迎。
And I would say that now I see that JSONB rush. 我现在看到了那种 JSONB 的冲刺。
Because I am speaking in many countries and I see that many people they have they ask me about JSON and they said that we don't know much about SQL. 因为我在很多国家演讲，看到很多人问我关于JSON的事，他们说我们对SQL了解不多。
We use JSON.
And what we need is just to have JavaScript instead of SQL.
This is very interesting career actually.
I have some thinking about this.
Because actually it's easy.
I can internally transform JavaScript to SQL and execute it.
But that's about my future project.
And JSONB is actually a main driver of Postgres popularity.
You see the create table JSON of the it's a common mistake.
They put everything into JSONB.
That is because people don't know about how JSONB performance.
I will talk about this later.
Another reason about this talk, nobody made the comparison between performance of JSONB operators.
So this talk I will explain which operator to use, what is better and so on.
And another reason is that I work 25 years in Postgres, maybe 26.
I started from 1995.
And almost all my projects, they connected to the extending Postgres to support this nonstructural data.
So we started from arrays, h store, full text search, now working on JSONB and SQL and this is my interest and I believe that JSON is very useful for Postgres community.
You see this picture.
This how popularity of four databases change over time.
The only database which grows is Postgres.
I use the official say official numbers from DB engine and relational popularity.
Postgres becomes popular from the time we committed JSONB into Postgres.
I believe that JSONB is one of the main driver of popularity.
And because No SQL people become upset and go to Postgres because Postgres got good JSON data type.
Our work on JSONB made possible SQL standard 2016.
So the success of Postgres made this, all other databases now have JSON and that's why we have SQL standard on this.
To me it's very important to have to continue work on JSON in Postgres.
Because we have many, many users.
These are numbers from PostGreSQL you see that the most popular is JSONB.
It's the biggest.
If we take the popularity in the telegram chart on PostGreSQL it's several thousand, any time you have several thousand people on land and JSON and JSONB is third popular award used by Postgres people.
The first is select, the second is SQL and the third is JSON.
This is like some argument that JSON is very popular in the Postgres community.
So we were working on several big projects, some of them already committed, some of them waiting for commit.
But now we change the priority for our development.
So we want to have JSONB the first class citizen in Postgres.
It's means we want to have efficient storage, select, update, good API, the reason for this is SQL JSON is important, of course.
Because this part of standard.
But actually people who work with Postgres, they have no idea to be compatible with Oracle or Microsoft.
I know that people, the startups use started with Postgres and never change it.
And JSONB is already a mature data type.
We have a load of functionality in Postgres and we have not enough resources in community.
To even to review and commit the patches.
You see that four years we have patches for SQL JSON functions.
JSON table will wait also four years.
Maybe for PG15 we'll have some committed.
But I understand that community just has no resources.
And my interest mostly is concentrate on improving JSONB, not standard.
So I mostly aware about Postgres users.
And I'm not very interested in the compatibility to Oracle or Microsoft SQL server.
So this is a popular mistake.
People put everything into JSON and that's not good idea.
You can see it very easy that ID outside of JSONB and ID inside.
If JSONB is grow, the performance is great degrade very quickly.
Don't do this.
We want to demonstrate the performance of nested containers.
You usually, we created simple tables with nested objects, and we just test several operators.
It's error operator.
Most people use error operator to access the key.
A hash arrow.
The new one is subscripting.
So you can you have like an array syntax.
Another one the force JSON pass.
The other one do you know which is better?
Nobody knows actually and we need to.
So we did a lot of experiments and now I will show you some graphics.
This is a raw JSONB size and execution time.
This is arrow operator.
JSONB grow and execution times grow.
So the reason for this I will explain later.
The same behavior actually is for other operator.
But it's a bit different.
Interesting that subscripting behave very good.
And we have the color is nesting level.
So we know execution time for the different nesting level.
This two kilobytes JSONB becomes toasted.
So we have degradation of performance.
But before, we have the constant and we have over here for nesting.
So the deeper we go, the performance is more worse.
Here is a slowdown, relative to the root level.
So the picture is also very strange relative to the root level for the same operator.
So this JSON path and subscription.
Arrow operator again shows some instability is not very predictable behavior.
If you see the slowdown relative to the extract pass operator, situation becomes a bit better,
a bit clearer because we see that JSON path is slowest.
And subscripting is the fastest.
Very unexpected behavior, but you know now this.
After 2 kilobytes, everything becomes more or less because the dominates.
You have major contribution to the performance.
This picture demonstrates best operator depending on size and nesting level.
So it's the same data, but in different picture, different format.
We see that arrow operator is good only for small JSONB and the root level.
I would say for root level you can safely use arrow operator.
For the second, for the first level, I would not use for the big JSONB.
Subscripting is the most useful good.
And JSON path, you see here, you see some JSON path.
But really JSON path is not about performance.
Because JSON path is very flexible for the very complex queries you need JSON path.
But for the simple queries like this, over here the JSON path is too big.
So all operators have common overhead, it's deTOAST and iteration time.
But arrow operator very fast for the small and root level because it has minimal initialization but it need to copy intermediate results to some temporary datums.
Let's see this one.
This picture how arrow operator executes.
So when you have TOAST, this is TOAST pointer, then you find key 1.
You find some value and you copy all this nested JSONB container into some intermediate data which go to the second execution.
So key 2 and then you copy string to result.
This example just for root in the first level.
But you can imagine if you have 9 levels, big more levels, you have to repeat this operation.
You have to copy all nested container to memory, to some intermediate datum.
This surprise for the abstraction.
In Postgres you can combine operator.
So this way this arrow operator works like this.
But extract path works different.
JSON path, they have they share the same schema.
Again, you have TOAST pointer.
You deTOAST and then you just copy string to result.
You find everything in one insight and there is no inside and there is no copy over here.
So the conclusion is that you can use safely arrow operator for root level of any size,
and for first level for small JSONB.
Then use subscripting and extract path for large JSONB and if you have higher nesting level.
This is my recommendation.
JSON path is slowest, but it's very useful for complex queries.
Now we want to analyze the performance of another very important queries is contains.
So you want to find some key value in JSONB.
So again, we have a table with arrays of various size.
From 1 to 1 million entries.
And we try to find several operations.
It's contains operator.
JSON pass match operator.
JSON path exist operator with filter.
And SQL, two variants of SQL.
One is exists and another one is optimized version of this one.
And we use this query.
Like we use we look for the first element existed.
Actually it's a zero.
And we look for the nonexistent operators which is minus 1.
And we see how different queries execute.
We see that if we apply search first element the behavior more or less the same, except two.
Contain separator is the fastest.
Before TOAST, before JSONB TOASTed we have constant and constant time.
And also we have much operator in lux mode.
JSON path has two modes, which you instruct interpreter of JSON path how to work with errors for example.
In lax mode.
Execution stops when you find the result.
And since zero is the first member in the array, it happens very fast.
But in strict mode, green, you have to check all elements.
Because in strict mode you have to see all errors, possible errors.
Fortunately lax mode is default.
So you see that search first element but for nonexistent element, behavior the same because you have to check all elements.
You check all elements, you can find minus zero.
And the difference in performance is just different overheads.
Operator.
So this is speed up relative to SQL exists.
And conclusions is that contains is the fastest.
SQL exists is the slowest.
And the performance between much and exist operators for JSON path depends how many items you have to iterate.
The most interesting, you see on all pictures, you see that performance very dependent on how big is JSON.
After JSON in 2 kilobytes, the performance degrade.
That's why we analyze the TOAST details.
This part I call the curse of TOAST.
This is unpredictable performance of JSONB.
People actually ask me why when they start the project we have very good performance,
but then we see sometimes that performance very unstable.
And this query, this example demonstrates unpredictable behavior.
So very simple JSON with ID and some array.
And first you have select very fast.
So we see that buffers hit 2,500 and some milliseconds.
After you update, very simple update.
You have 30,000 buffers.
And 6 milliseconds.
So people asking me why it happens, but it happens because rows gets TOASTed.
So TOAST is a very useful technology in Postgres which allows you to store very long objects.
We have limitation for 8 kilobyte page size.
We have tuple limit to 2 kilobytes.
Everything that's bigger to 2 kilobytes we move to storage.
And we have implicit join when we get value.
But situation is much worse, I will show later.
But here is explanation.
You can install very useful extension page inspect and see that regional JSON, which stored in line, not TOASTed in heap, we have 2,500 pages.
And just 4tuples per page.
After update, we see very strange that we have now 64 pages with 157 tuples per page.
How this happens?
Because with long JSON replaced by just tuple TOAST pointer.
So tuple becomes very small.
But number but everything move to the TOAST.
And we can find using this query, we can find the name of TOAST relation very easy.
And then we can inspect how about chunks where we store this long query in the TOAST.
And access to the TOAST requires reading at least 3 additional buffers.
Two TOAST index buffers, because you access TOAST not directly, but using the B tree index.
So you access 2 TOAST index buffers and one TOAST from heap buffer.
So easy calculation you explain that why we have 30,000 pages after update.
We have 64 buffers, and overhead 3 buffers multiplied by number of rows.
10,000.
So this explains performance of the small update.
And if you know what is TOAST.
I can explain it's very detailed.
TOAST compressed and then split into 2 kilobyte chunks and stored in the normal heap relations.
You just don't see.
It's hidden from you.
And how to access these chunks.
You have to use index.
Over here only bytes from the first chunk.
You hid 3, 4, 5 or additional blocks.
That's the problem of TOAST.
And TOAST also very complicated algorithm.
It use four passes.
So Postgres try to compact tuple to 2 kilobytes.
We tried to compress the longest fields.
If it's not compressed, if the result of tuple still more than 2 kilobytes, it replace fields by TOAST pointer and fields move to the TOAST relation.
Actually you see that pass 1, pass 2, pass 3, pass 4.
So it's not easy.
The original tuple replaced by this.
This is plain, some field which not touched.
This is compressed field and 4 tuple pointers which pointed to the TOAST storage.
We have here.
And when you access, for example, this one, you have to read all this one.
Even if you access plain attribute, you don't touch all this TOAST.
But once the attribute is TOASTed, you have to combine all these chunks.
First you need to find all chunks, combine to one buffer.
And then decompress.
So a lot of overhead.
Let's see this example.
Example is very easy.
We have hundred JSONB of different sizes and JSONB looks like key 1 very long key 2 array,
key 3 and key 4.
We measure time of arrow operator.
We actually we repeat 1,000 times in query.
Then to have a more stable result.
This result.
You see?
So we generate the query.
We execute 1,000, and then result the time, divide by 1,000, to have more stable time.
And we see the key access time literally increase JSONB size.
Regardless of size and position.
And this is very surprise for many people because they say I have just one small key.
Why I my access time is so big?
Because everything after here after the hundred kilobytes, everything in TOAST.
And to get this small key, you have to deTOAST all this JSONB.
And then decompress.
You see three areas.
One is in line.
The performance is good, and time is constant.
The second one is expressed in line.
When Postgres success to compress and put into inline.
So hundred kilobytes is actually 2 kilobytes compressed.
Because here's a row.
After compressed 100 kilobyte becomes 2 kilobyte.
And you have some growth access time because you have to decompress.
And after 100 kilobytes, everything toasted.
Here is a number of blocks.
200 kilobytes, you see no additional block to read because everything in line.
After you read more, more, more, more blocks, you see 30 blocks, it's too much.
>> Obviously these blocks are it might be worrying [off microphone] have you done a comparison to Mongo I'll ask the question again.
Have you done a comparison with Mongo and in what areas does Mongo suffer these same issues?
>> OLEG BARTUNOV: The last slide will be about some comparison with Mongo because that's all people is interested.
Yes.
This is the same.
Now we see this is compressed size.
So we see that only two areas.
Inline this is size 2 kilobytes.
After 2 kilobytes it would compress in line.
In line they are more clearly seen because of their size is compressed 2 kilobytes.
And the problem is access time doesn't depends on the key size and position.
Everything suffers from the TOAST.
Another problem is partial update.
Because people also complain that I just want to update small key.
Why the performance very bad?
Again, again, because Postgres TOAST, because TOAST mechanism, algorithm works with JSONB as a black box.
Because it was developed by when all data types were atomic.
But now JSONB has a structure as other data type.
And TOAST should be more smart.
But currently TOAST storage duplicated when we update, well traffic increased and performance becomes very slow.
Also you see the example.
We have hundred gigabytes heap and relation 7.
After update we have TOAST table doubled but also we have 130 megabytes well traffic.
After small update.
Because Postgres doesn't know anything about structure of JSONB.
It just black box, double size and this is problem.
So we have our project started this year in the beginning of this year.
JSONB deTOAST improvement and our goal, ideal goal.
So we won't have new dependency on JSONB size and position.
Our access time should be proportional to the nesting level and update time should be proportional to nesting level.
And the key size what we update.
Not the whole JSONB size but what we update only.
Original TOAST doesn't use inline.
Once you TOAST, we have a lot of space in heap just free.
So we want to utilize inline as much as possible because access to inline many times faster than to the toast.
And we want to compress long fields in TOAST chunks separately for independent access and update.
So if you want to update something, you don't need to touch all chunks.
You need to find some chunk updated and log.
That's all.
But this ideal.
And we have several experiments.
So woo have a partial decompression.
We saw JSONB object key by length.
So short keys, they stored in the beginning.
Partial deTOAST, partial decompression, in lapse line toast, compressed fields TOAST not much time.
So I just say their names.
But inplace updates.
And here we see the results.
This is a master.
How master behave.
After, for example, after partial depression, after the partial decompression, some keys becomes faster.
Here is all keys behave the same.
But after partial decompression some keys become faster because they're in the beginning and decompressed faster.
After the sorting keys, we see another keys came down.
Because key 3 for example was blocked by long key 2.
Was blocked after the sorting key 3 becomes behind the long objects.
And this is, you see, it becomes lower.
And after all this experiment, we get very interesting results, very good result.
So we have still growing time but this we understand for the big arrays.
Big arrays.
And the first element in array we access faster than the last, of course.
But what to do with this?
We find it's another problem.
But you see some very simple optimization with still stay with heap with TOAST, we can find like several orders of magnitude performance gain.
Here is a very interesting picture how much different optimization gain to the performance.
So we see that this is the short keys.
Key 1 and key 3 for example here.
Key 3 green sorting.
Because it was blocked by key 2 but after sorting keys it got a lot of performance gain.
And so on.
So it's easy to interpret this picture.
Slides available but not much time.
And if we return back to this popular mistakes, the mistakes becomes not very serious.
Because now this ID, which stored inside JSONB, not growing infinitely, but stay constant, still have some overhead, but it's okay, more or less.
After all this experiment.
We have also make some experimental sliced deTOAST.
To improve access to array element stored in chance.
And the last you see this arrays elements, they now not growing infinitely but this is very experimental.
Update.
Update is very, very painful process in Postgres.
And for JSONB especially.
Here is a master.
After the shared TOAST, shared TOAST means we update only selected chunks.
Other chunks were shared.
After the shared TOAST we have good results and only on the last array elements still grow because the last element in the array.
In in place update, we have I think it's good, not good results for updates.
Again for updates we have again several orders of magnitude.
Updates is very important, because people use JSONB in OETP environment.
So update is very important.
Access is good for analytical processing.
But for LTP update is very important.
This is a number of blocks read.
You see clearly we have much less blocks to read.
Just to remind you, this is not linear scale, this is algorithmic scale.
This is WAL traffic.
In master you have very, very big WAL traffic.
In shared it's smaller.
And here we have very controlled WAL traffic.
You looked only what you update.
The remain people asking what if I use JSONB and relational structure, which is better?
So we made several again access the whole document testing.
So this JSONB, this relational tables for relational, you need to have join, for JSON you don't need join and we have several queries and this is the result picture.
So you see that this is a very, very fast the darker blue is good.
This is bad.
To access the whole document in JSONB it's no surprise, very fast.
Because you don't need to just need to read it.
And for but when you want to transfer JSONB, you have problem.
Because you need to convert JSONB to the text.
Conversion to the text in Postgres is also painful.
You need to check all bits, you know, to characters so it's not easy operation and we see for the large JSONB the time is not very good and it becomes also here.
Here we made experiment when we don't need to transfer in textual forum as text.
This is called UB JSON we transfer to the client just binary transfer.
We see it becomes better.
There is no degradation.
For the relational, you see the problem.
We transfer this is the time for select.
This is for select transfer and this is transfer binary.
There is a method, for arrays you can transfer binary.
And clearly we see that JSONB here is the winner.
And this explain why it's so popular.
Because micro service, what is a micro service?
It's a small service which expects some predefined query, some aggregate.
And JSONB is a actually aggregate.
You don't need to join data from different tables.
You just have aggregation.
And microservice access this JSONB and performance is very good.
Very simple.
So if you use microservice architecture you'll just happy with JSONB.
That's popular.
But when you access key and update, you have a bit different result.
Because the first one is the last one is select.
The relational, current situation, and this situation with JSONB after all our optimization.
So you see that after all optimization, as fast as relational access.
We understand that for JSONB to get some key, you have to do some operation.
But for relational, you just need to get some you don't need any overhead.
And very nice is that after our optimization, we behave the same as relational.
But for current situation like this.
So if you want to update especially, update access key, relational is the winner.
Our optimization helps a lot.
This is slowdown.
You see that JSONB slow down against this relational.
But after our optimization, we have you see like the same.
Like relational.
And the same is update slowdown.
So for update we still have some this is JSONB original in master, and here is our.
So our optimization helps for updates.
And also WAL traffic.
So we're here for master, we hit a lot of WAL traffic.
We look for a lot.
This is relational and this is our optimization.
There looks like the same.
Also we have access array member.
Very popular operation.
We have array and you want to get some member of this array.
We have first key, middle key and the last key.
And here's relational, optimized, and non optimized JSONB.
So we see access array member is not very good for JSONB, but with our optimization,
again, again we approach to the relational.
And update array member, we also compare how to update array member.
And here is JSONB.
This is JSONB optimize and this is relational.
Of course when you update array member in relational table, it's very easy.
You just update one row.
And for the JSONB, you update the whole JSONB.
We can be very big.
But our optimization helps a lot again.
This is a WAL.
And conclusion is that JSONB is good for full object access.
So microservices.
It's much faster than relational way.
In relational way you have to join, have you to aggregate and very difficult to tune the process.
With JSONB you have no problem.
Also JSONB is very good for storing metadata.
In short, metadata in separate JSONB field.
And currently PG14 not optimized, as I showed you, for update.
And access to array member.
But we demonstrated all our optimization, which give which resulted in orders of magnitudes for select and update.
And the question is how to integrate now all this, our patches, to the Postgres.
And the first step is to make a data type aware TOAST.
Because currently TOAST is the common for all data type.
But we suggest that TOAST should be extended.
So data type knows better how to TOAST it.
And that allows us to, allows many other developers give a lot of performance improving.
We have example when we improve streaming.
You know, some people want to stream data into the Postgres.
For example, movie.
It's crazy, but they stream there.
Before you image how it behaves slowly because it's logged every time.
You add one byte and you look all one gigabyte.
After our optimization we have special extension, it works very fast.
We look only this one byte.
That's all.
Because we made a special for byte here, special TOAST.
So we need to the community accept some data type.
Just two slides.
So we have to do.
On physical level we provide random access to keys and arrays.
On physical level this is easy.
We already have sliced deTOAST.
We need to do some compression.
But it's most important to make it at the logical level.
So we know how to which toast chink we need to work on logical level.
And this number of patches.
This not exists, not exists yes.
But this all our patches and our roadmap how to work with community to submit all this picture.
All this our result.
And references.
And I invited you to join our development team.
This is not a company project, this is open source community project.
So everybody invited to join us.
This is what asked me, Simon asked about non scientific comparison Postgres with Mongo.
I said nonscientific because scientific benchmark is very, very complicated task.
Very, very.
But here is nonscientific.
Mongo need double size of Postgres because Mongo keep uncompressed data in memory.
And we see that this is manual progress.
After our optimization, we have performance better than Mongo.
But if we turn on parallel support, because all this without any parallelism to compare.
But after the parallel support we have very fast Postgres compared to Mongo.
But as I said, this is Mongo, when memory is just 4 gigabytes, and Mongo is not very good when you don't have enough memory.
So it behave like the Postgres.
Postgres is much better.
It works with memory.
So that means that we have our community, our community have a good chance to attract Mongo users to the Postgres.
Because Postgres is a very good, solid, database.
Good community.
We're all open source, independent.
And we have JSON.
Just need to better performance and to be more friendly to the young people who started working with Postgres.
This is picture of my kids.
They climb trees.
And sometimes they tear their pants.
And I have two options.
I can forbid them to do this.
I can teach them.
So let's say that JSON is not the wrong technology.
Let's make it a first class citizen in Postgres.
Be friendly.
Because still some senior Postgres people still say that oh, relational database, relations, you need just to read these books.
But young people, startups, they don't have time.
They can hire some very sophisticated senior database architecture.
They want to make their business.
They need JSON.
So my position is to make Postgres friendly to these people.
This is actually our duty.
Database should be smart.
People should work should do their project.
So that's why I say that at least at the end, we are universal database.
So I say that all you need is just Postgres.
With JSON we will have a lot of fun, a lot of new people, and our popularity will continue to grow.
That is what I want to finish.
Thank you for attendance.
[ Applause ] I think we have not much time for questions and answers.
I will be available the whole day.
You can ask me, you can discuss with me what I'm very interested in your data.
In your data, in your query.
You know it's very difficult to optimize something if you don't have real data and a real query.
So I don't need personal data.
If you can share, I will be very useful and help us.