9bNine Bits

PostgreSQL JSONB Internals and Indexing

PostgreSQL's jsonb type stores JSON documents as parsed binary values rather than raw text, so reads and comparisons skip re-parsing at the cost of a bit more work on write. It exposes operators for containment, key existence, and path lookup, and those operators are accelerated either by a GIN index on the jsonb column or by a plain B-tree index on a specific extracted expression. The right index depends entirely on which operators a query actually uses.

 

PostgreSQL JSONB Internals and Indexing

After the JSON datatype became one of the primitive data types in PostgreSQL, there have been many improvements and many related materials have been also published. However, there are few which deal with indexes for JSON type and their performance. In this post, we’ll describe JSON datatype in brief and discuss its indexes and their performance.

PostgreSQL JSONB Internals and Indexing

 

What is JSON?

JSON(JavaScript Object Notation) is the open-standard format for interchanging data objects which consist of Name/Value pairs. Although JSON is independent with programing languages, as it follows JavaScript grammar format, it is text format familiar to C-family languages. So, it is very easy for programmers to read, write, also easy for machine to parse and generate, so that lots of programing languages support JSON.

 

JSON in PostgreSQL

Since PostgreSQL 9.2, JSON has been a primitive data type in PostgreSQL. One can store JSON object in a column of JSON datatypes, and by using this one can use PostgreSQL as a schema-less NoSQL database. One of the defects of relation databases is that it is not flexible for column addition/deletion. But using JSON datatypes, it can be flexible with column addition/deletions.

Another datatype related to JSON object, JSONB, was also added in PostgreSQL 9.4. The JSON datatype stores JSON objects in text format, so it has disadvantages to parse and analyze the text-format JSON objects every time it is processed. In addition, it stores unnecessary white spaces and duplicated keys. JSONB datatype converts text-format JSON object into a binary format. And doing that, it removes unnecessary white spaces and duplicated keys, and sorts the keys. Because of these pre-processing, JSONB consumes more spaces and processing power than JSON. However data processing for JSONB becomes much more efficient. Of course, all operators of JSON and additional Containment and Existence operators are supported for JSONB, and moreover it has an advantage to use GIN index.

 

GiN Index

GiN( Generalized Inverted Index ) is inverted index, a structure that has only one single index entry per a key and store the mapping information(posting list) of all key row to have the same value in the index entry. In postgreSQL, the key is stored in the index entry and mapping information for the key is stored in the posting tree. To search the index entry and posting tree is using a B-Tree. Therefore, GIN is useful when an index must map many values to on row, such as indexing array, documents.

 

JSONB and Indexes

When we use ->> operator of JSONB, PostgreSQL can use B-tree or Hash index for processing the operations. ->> oerator returns the value of the specified attribute in text format. PostgreSQL can use indexes for the text results as compare operands. GIN index can be used by GIN jsonb operator class.

 

GIN jsonb operator classes

Default operator class jsonb_ops supports existence operators( ?, ?&, ?| ) and containment operator( @> ). And jsonb_path_ops supports containment operator only. Therefore GIN index is only possible to search for that have a particular key or key-values.

[PostgreSQL Doc : jsonb operators ]

 

We conducted some tests for measure the performance of JSONB and its indexes as follows. We used pokec dataset ( https://snap.stanford.edu/data/soc-pokec.html ), which is made from the SNS in Slovakia and published by Stanford University. It has 1.6 million user profiles. We built indexes on the hobbies attribute of profiles.

 

CREATE INDEX idx_btree_hobbies ON jsonTbl USING BTREE ((jsb->>‘hobbies’)); CREATE INDEX idx_btree_hobbies ON jsonTbl USING HASH ((jsb->>‘hobbies’));

SELECT count(*) FROM jsonTbl WHERE jsb->>’hobbies’ = ‘snowboarding’; SELECT count(*) FROM jsonTbl WHERE jsb->>’hobbies’ = ‘snowboarding’ OR jsb->>’hobbies’ = ‘varenie’;

 

CREATE INDEX idx_btree_hobbies ON jsonTbl USING GIN ((jsb->‘hobbies’));

SELECT count(*) FROM jsonTbl WHERE jsb->’hobbies’ ? ‘snowboarding’; SELECT count(*) FROM jsonTbl WHERE jsb->’hobbies’ ? ‘snowboarding’ OR jsb->’hobbies’ ? ‘varenie’;

 

CREATE INDEX idx_btree_hobbies ON jsonTbl USING GIN (jsb jsonb_ops); CREATE INDEX idx_btree_hobbies ON jsonTbl USING GIN (jsb jsonb_path_ops);

SELECT count(*) FROM jsonTbl WHERE jsb @> ‘{“hobbies” :“snowboarding”}; SELECT count(*) FROM jsonTbl WHERE jsb @> ‘{“hobbies”:”snowboarding”}’ OR jsb @> ‘{“hobbies”:”varenie”}’;

 

(Unit : ms)

One key

Every Key reference
Btree Hash GIN GiN jsonb_ops

GiN jsonb_path _ops

Index building time

28,420 39,449 10,826 417,689 126,889

Index size

120 72 63 941 494

Insert

33,046 25,855 28,222 189,765 86,339

update

28,857 43,230 30,929 203,395 92,230

delete

31,153

31,337 27,751 26,770 30,127

select 1

1 1 1 42 1

22 rows

select 2

19 27 45 82 51

5262 rows

 

According to the result above, BTREE performed the best in search for single attribute, Gin index outstands in building time and the size of the index. Jsonb_path_ops performed better in searching every single attribute than jsonb_ops. Although jsonb_path_ops is index for all attributes, it performed well compare to the single key indexes, also index size and building time of jsonb_path_ops were not good enough as it has more than 40 attributes.

 

Conclusions

BTREE index performed the best in indexing search for single attribute. HASH index also performed well, nonetheless not providing WAL (Write Ahead Log) is critical to the abnormal termination. GIN index benefits in indexing all attribute with a single index, however it performances poorer in indexing a single key than BTREE and HASH index.

As a result, in the choice of index for jsonb type, if the index searches a few predetermined attributes, BTREE is the best choice, however if the index searches arbitrary attributes prefers GIN index. In the case of using GIN index, use jsonb_path_ops as a class checking whether it simply has a key-value, but in order to check not only it has a key-value also existence of the key, better to use jsonb_ops.

 

 

Tags: BTREE , GIN , Index , Internals , JSON , JSONB , PostgreSQL

Common questions

Should I use json or jsonb for a new column?

Use jsonb for almost all cases. It is faster to query because it is already parsed, and it is what PostgreSQL's indexing operators and operator classes are built around. Use json only when exact preservation of input text, key order, or duplicate keys matters.

Does a GIN index on a jsonb column speed up every query against it?

No. A GIN index only accelerates the operators its operator class supports: containment, existence, and jsonpath operators with jsonb_ops, or just containment and jsonpath with jsonb_path_ops. A query that extracts a key with ->> and compares it to a scalar needs a separate B-tree expression index.

What is the difference between jsonb_ops and jsonb_path_ops?

jsonb_ops, the default, indexes every key and value separately and supports existence operators (?, ?|, ?&) along with containment. jsonb_path_ops indexes hashed paths instead, supports only containment and jsonpath operators, and produces a smaller, more selective index.

Can I index a single key inside a jsonb column?

Yes, with a B-tree expression index such as CREATE INDEX ON t ((data->>'key')). It supports equality and range comparisons on that extracted value and can be declared unique, which a GIN index on the whole column cannot do.

Does jsonb preserve the order of keys in an object?

No. jsonb reorders object keys internally and keeps only the last value when a key is duplicated in the input. If exact textual order or duplicate keys must be preserved, use the json type instead.

Sources

  1. PostgreSQL Documentation: JSON Types
  2. PostgreSQL Documentation: GIN Indexes
  3. PostgreSQL Documentation: TOAST

About this page

Last reviewed October 30, 2026 by the Nine Bits editors. Read our editorial policy or report an error.