Code Review Videos > Postgres JSON vs JSONB Performance

Postgres JSON vs JSONB Performance

When it comes to storing JSON data in Postgres there are two options: JSON and JSONB.

I guess you could also store the raw text version of your JSON, but then you lose all the functionality that the Postgres JSON and JSONB data types provide.

While both data types appear to serve the same purpose, the official docs say they have some fundamental differences that can impact database performance.

Disclaimer

I am not a SQL expert.

I am not a Postgres expert, nor a benchmarking expert.

I am an inquisitive amateur.

If there are ways to improve this approach, do please leave a comment and let me know.

JSON vs JSONB – Similarities and Differences

json stores JSON data as text, while jsonb stores JSON data in a binary format. This means that jsonb takes up less space and is (apparently) faster to query than json. However, jsonb requires more processing time to insert or update data compared to json.

Another difference is that jsonb values are always well-formed JSON, while json values can be any text that is a valid JSON object or array. This means that json allows storing JSON data that is not strictly conforming to the JSON specification, while jsonb enforces stricter rules and rejects any non-conforming data.

As best I can tell, JSON that is not strictly conforming means:

// strict - everything is quoted:
{ "name": "Billy Bush", "age": 30, "email": "billy.bush@example.com" }

// not strict - keys are not quoted:
{ name: "Billy Bush", age: 30, email: "billy.bush@example.com" }Code language: JavaScript (javascript)

From what I have read, json is suitable when storing data that is read frequently and updated infrequently, while jsonb is a better choice when frequent read and write operations are expected, or when working with larger datasets.

However the official Postgres docs for JSON Types says:

In general, most applications should prefer to store JSON data as jsonb, unless there are quite specialized needs, such as legacy assumptions about ordering of object keys.

JSON Types in Postgres – official docs

I should probably have just trusted this, rather than going down a rabbit hole. However, down that rabbit hole I did go, and so I figured I would share the results to save you the bother.

Performance Comparison

When the official docs tell you to do something, it’s probably “good enough”, right?

Yeah. Probably.

But for some reason that now eludes me, I decided to dive in a little further and actually do some performance testing to see how the numbers came out in my own setup.

Performance Benchmark Setup

For this benchmark test I used a Dockerised version of Postgres.

There are thoughts and opinions on using Docker for databases, but honestly, for my needs, a Docker instance of Postgres is more than sufficient.

Here is my docker-compose.yaml file:

version: "3.9"
services:
  db:
    image: postgres:15.2
    container_name: crv_postgres_benchmark
    ports:
      - "5532:5432"
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: my_secure_password
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/1-schema.sql
      - ./test_json.sql:/test/test_json.sql
      - ./test_jsonb.sql:/test/test_jsonb.sqlCode language: YAML (yaml)

This is a Docker Compose file that defines a service called db which uses the official PostgreSQL Docker image postgres:15.2. This is the latest at the time of writing. There’s no special reason to be using the very latest version of Postgres, other than … why not?

I have explicitly named the container as crv_postgres_benchmark to make things easier later on in this example.

The db service is configured to expose the PostgreSQL database container’s port 5432 as the host port 5532, so that I can connect to it using my computer’s network IP address and that specific port number.

The environment section defines my desired database name, database user name, and password for that user. Note that these three enviornment variables are from the postgres:15.2 docker image, and I am overriding their defaults. Change these up as you see fit.

The volumes section sets up three volume mounts between the host machine and the container.

The first one mounts ./init.sql file to /docker-entrypoint-initdb.d/1-schema.sql in the container, which means that the init.sql file will be executed as part of the database initialisation process.

Again, this is a thing that is specific to the Postgres Docker image, and I am ‘hooking into it’ so that I can provide my own SQL that will run when the container first comes online.

The second and third volume mounts test_json.sql and test_jsonb.sql files to /test/test_json.sql and /test/test_jsonb.sql respectively in the container, allowing the files to be accessed from within the container. We will use these to run our benchmarks.

Connecting To The Database

You will need to do some of the steps further below, but I wanted to show this now as this is how it would look inside a Database GUI tool such as JetBrain’s DataGrip when setting up the “local to container” database connection:

connecting to a local postgres docker container in jetbrains datagrip

SQL File Content

As the volumes section describes three SQL files that we wish to be available inside the resulting Docker container instance, we must also provide these files before starting the container.

postgres json vs jsonb directory contents setup

For simplicity, the three files are placed alongside the docker-compose.yaml file.

init.sql

The init.sql file is as follows:

CREATE TABLE test_json (
   id serial primary key,
   data json
);

CREATE TABLE test_jsonb (
   id serial primary key,
   data jsonb
);


INSERT INTO test_json (data)
SELECT ('{ "name": "CRV Tester", "random_number": ' || (random() * 100)::int || ', "random_string": "abc' ||
        (random() * 100)::int ||
        'xyz" }')::json
FROM generate_series(1, 1000000);


INSERT INTO test_jsonb (data)
SELECT ('{ "name": "CRV Tester JSONB", "random_number": ' || (random() * 100)::int || ', "random_string": "qwe' ||
        (random() * 100)::int ||
        'rty" }')::jsonb
FROM generate_series(1, 1000000);Code language: SQL (Structured Query Language) (sql)

We go from fairly straightforward (the create table syntax) to a lot more involved. But let’s break this down anyway.

CREATE TABLE test_json (
   id serial primary key,
   data json
);

CREATE TABLE test_jsonb (
   id serial primary key,
   data jsonb
);Code language: SQL (Structured Query Language) (sql)

The SQL code creates two new tables named test_json and test_jsonb.

The test_json table has two columns:

  • id with data type serial which is used as a primary key for the table.
  • data with data type json which is used to store JSON data in the table.

The test_jsonb table also has two columns:

  • id with data type serial which is used as a primary key for the table.
  • data with data type jsonb which is used to store binary JSON data in the table.
INSERT INTO test_json (data)
SELECT ('{ "name": "CRV Tester", "random_number": ' || (random() * 100)::int || ', "random_string": "abc' ||
        (random() * 100)::int ||
        'xyz" }')::json
FROM generate_series(1, 1000000);Code language: SQL (Structured Query Language) (sql)

This SQL statement is inserting 1,000,000 rows of JSON data into the test_json table. The data inserted is generated using a combination of random numbers and strings.

The SELECT statement generates the data using the random() function, which generates a random value between 0 and 1. The generate_series(1, 1000000) function generates a series of 1 million values starting from 1, creating 1 million rows of data.

The generated JSON data has three fields:

  • name“: a string value set to “CRV Tester“.
  • random_number“: an integer value generated by multiplying a random number between 0 and 1 by 100 and casting it to an integer.
  • random_string“: a string value that concatenates the string “abc” with a random number between 0 and 1 multiplied by 100, and then concatenates “xyz” to the end.

Finally, the ::json syntax is used to explicitly cast the generated string to JSON type and insert it into the data column of the test_json table.

This ends up with data looking like this:

JSON benchmark test data

The JSONB variant is really similar, but subtly different:

INSERT INTO test_jsonb (data)
SELECT ('{ "name": "CRV Tester JSONB", "random_number": ' || (random() * 100)::int || ', "random_string": "qwe' ||
        (random() * 100)::int ||
        'rty" }')::jsonb
FROM generate_series(1, 1000000);Code language: PHP (php)

The first change is that we insert into test_jsonb, not test_json.

The second, more subtle difference is that we cast to ::jsonb instead of ::json at the end of the SELECT statement.

Beyond that, the only other difference is that I changed the name to “CRV Tester JSONB” – because it’s easier for me to quickly see which table I am working with from a human perspective.

This results in very similar data in the test_jsonb table:

JSONB benchmark test data

test_jsonb.sql

We’re starting with updating the JSONB data type because it is the easier of the two from a query perspective. At least, that’s my opinion.

Inside the test_jsonb.sql file we have the following contents:

BEGIN;
UPDATE public.test_jsonb 
    SET data = jsonb_set(data, '{random_number}', to_jsonb((data->>'random_number')::int + 1)) 
    WHERE id = (SELECT trunc(random() * 1000000)::int + 1);
SELECT * FROM public.test_jsonb WHERE id = trunc(random() * 1000000)::int + 1;
COMMIT;Code language: PHP (php)

This SQL code performs the following actions:

  1. Starts a transaction using the BEGIN statement.
  2. Updates a row in the “public.test_jsonb” table. The update sets a new value for the “data” column using the jsonb_set function. The function takes three arguments: the current value of the “data” column, the path of the JSON key to update (in this case, “{random_number}“), and the new value to set for that key. The new value is generated by converting the current value of the “random_number” key to an integer using the “->>” operator, adding 1 to it, and converting the result back to JSONB using the to_jsonb function.
  3. The update is applied to the row where the “id” column matches a randomly generated integer between 1 and 1,000,000 (inclusive). The randomly generated integer is obtained using a subquery that generates a random float between 0 and 1, multiplies it by 1,000,000, takes the integer part using the trunc function, and adds 1 to it.
  4. The SELECT statement retrieves the updated row by selecting all columns from the “public.test_jsonb” table where the “id” column matches another randomly generated integer between 1 and 1,000,000 (inclusive).
  5. Finally, the transaction is committed using the COMMIT statement, which makes the changes permanent.

I’m not sure if I have to wrap my statements in a transaction, but because I’m going to be using pgbench for benchmarking, and that tool measures in “transactions per second”, I figured I should do anyway.

test_json.sql

The SQL for test_json.sql is almost the same as the SQL above for test_jsonb.sql.

Here’s the exact SQL:

BEGIN;
UPDATE public.test_json
    SET data = jsonb_set(data::jsonb, '{random_number}', to_jsonb((data->>'random_number')::int + 1))::json
    WHERE id = (SELECT trunc(random() * 1000000)::int + 1);
SELECT * FROM public.test_json WHERE id = trunc(random() * 1000000)::int + 1;
COMMIT;Code language: SQL (Structured Query Language) (sql)

It does look very similar. However, it differs in two ways.

Firstly it operates against the public.test_json table. That’s the obvious change.

The less obvious change is the explicit casting to JSONB and then back to JSON.

I may be doing this wrong, but I couldn’t figure out a better way. I only wanted to update the random_number part of my JSON.

This SQL code updates a row in the “public.test_json” table by modifying the value of the “data” column. Specifically, the update sets a new value for the “random_number” key in the “data” column by incrementing its current value by one.

The UPDATE statement takes the following actions:

  1. It uses the jsonb_set function to modify the “data” column. The function takes three arguments: the current value of the “data” column, the path of the JSON key to update (in this case, “{random_number}“), and the new value to set for that key. The new value is generated by converting the current value of the “random_number” key to an integer using the “->>” operator, adding 1 to it, and converting the result back to JSONB using the to_jsonb function.
  2. It casts the resulting JSONB value to a JSON data type using the “::json” operator.
  3. It applies the update to the row where the “id” column matches a randomly generated integer between 1 and 1,000,000 (inclusive). The randomly generated integer is obtained using a subquery that generates a random float between 0 and 1, multiplies it by 1,000,000, takes the integer part using the trunc function, and adds 1 to it.

The purpose of the CAST operator “::json” is to convert the resulting JSONB value to a JSON data type.

So it feels like this is always going to be slower on the UPDATE simply because of this overhead. But I don’t know a better way to work with JSON.

Anyway, those are the three SQL files. With those in place we can actually go ahead and bring up the Docker test environment.

Docker Container Management

Given the above, we should be good to bring up the Dockerised Postgres database with a single command:

docker-compose up

This will start up the container and take over the terminal session, displaying any logs as they are output:

➜  postgres-json-jsonb-test docker-compose up
Creating postgres-json-jsonb-test_db_1 ... done
Attaching to postgres-json-jsonb-test_db_1
db_1  | The files belonging to this database system will be owned by user "postgres".
db_1  | This user must also own the server process.
db_1  | 
db_1  | The database cluster will be initialized with locale "en_US.utf8".
db_1  | The default database encoding has accordingly been set to "UTF8".
db_1  | The default text search configuration will be set to "english".
db_1  | 
db_1  | Data page checksums are disabled.
db_1  | 
db_1  | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db_1  | creating subdirectories ... ok
db_1  | selecting dynamic shared memory implementation ... posix
db_1  | selecting default max_connections ... 100
db_1  | selecting default shared_buffers ... 128MB
db_1  | selecting default time zone ... Etc/UTC
db_1  | creating configuration files ... ok
db_1  | running bootstrap script ... ok
db_1  | performing post-bootstrap initialization ... ok
db_1  | syncing data to disk ... ok
db_1  | 
db_1  | 
db_1  | Success. You can now start the database server using:
db_1  | 
db_1  |     pg_ctl -D /var/lib/postgresql/data -l logfile start
db_1  | 
db_1  | initdb: warning: enabling "trust" authentication for local connections
db_1  | initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
db_1  | waiting for server to start....2023-02-28 14:37:51.750 UTC [47] LOG:  starting PostgreSQL 15.2 (Debian 15.2-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
db_1  | 2023-02-28 14:37:51.750 UTC [47] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1  | 2023-02-28 14:37:51.752 UTC [50] LOG:  database system was shut down at 2023-02-28 14:37:51 UTC
db_1  | 2023-02-28 14:37:51.754 UTC [47] LOG:  database system is ready to accept connections
db_1  |  done
db_1  | server started
db_1  | CREATE DATABASE
db_1  | 
db_1  | 
db_1  | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/1-schema.sql
db_1  | CREATE TABLE
db_1  | CREATE TABLE
db_1  | INSERT 0 1000000
db_1  | INSERT 0 1000000
db_1  | 
db_1  | 
db_1  | waiting for server to shut down...2023-02-28 14:37:59.346 UTC [47] LOG:  received fast shutdown request
db_1  | .2023-02-28 14:37:59.347 UTC [47] LOG:  aborting any active transactions
db_1  | 2023-02-28 14:37:59.347 UTC [47] LOG:  background worker "logical replication launcher" (PID 53) exited with exit code 1
db_1  | 2023-02-28 14:37:59.350 UTC [48] LOG:  shutting down
db_1  | 2023-02-28 14:37:59.351 UTC [48] LOG:  checkpoint starting: shutdown immediate
db_1  | 2023-02-28 14:38:00.120 UTC [48] LOG:  checkpoint complete: wrote 16286 buffers (99.4%); 0 WAL file(s) added, 0 removed, 26 recycled; write=0.319 s, sync=0.446 s, total=0.770 s; sync files=260, longest=0.272 s, average=0.002 s; distance=422016 kB, estimate=422016 kB
db_1  | 2023-02-28 14:38:00.135 UTC [47] LOG:  database system is shut down
db_1  |  done
db_1  | server stopped
db_1  | 
db_1  | PostgreSQL init process complete; ready for start up.
db_1  | 
db_1  | 2023-02-28 14:38:00.160 UTC [1] LOG:  starting PostgreSQL 15.2 (Debian 15.2-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
db_1  | 2023-02-28 14:38:00.160 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db_1  | 2023-02-28 14:38:00.160 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db_1  | 2023-02-28 14:38:00.160 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1  | 2023-02-28 14:38:00.162 UTC [65] LOG:  database system was shut down at 2023-02-28 14:38:00 UTC
db_1  | 2023-02-28 14:38:00.165 UTC [1] LOG:  database system is ready to accept connectionsCode language: Shell Session (shell)

To stop this, press ctrl+c:

^CGracefully stopping... (press Ctrl+C again to force)
Stopping crv_postgres_benchmark ... doneCode language: Shell Session (shell)

But this does leave an exited container behind:

➜  postgres-json-jsonb-test docker ps -a

CONTAINER ID   IMAGE                                                                                         COMMAND                  CREATED              STATUS                      PORTS                                   NAMES
37cdbb929a0c   postgres:15.2                                                                                 "docker-entrypoint.s…"   About a minute ago   Exited (0) 51 seconds ago                                           crv_postgres_benchmark
Code language: Shell Session (shell)

So this is a bit of a problem as if you want to clean the slate and start again, the next time you run a docker-compose up, it’s going to remember your previous SQL data inside that previous container.

To delete and start again, run:

docker rm $(docker ps -a -f status=exited -q)

That’s going to find any exited containers and remove them.

That means when you then run docker-compose up again, everything will start fresh.

Running The Benchmarks

With the database set up and populated, it’s time to get down to what the whole purpose of this experiment was in the first place: benchmarking.

To benchmark the read/write performance of PostgreSQL’s json and jsonb data types, we will use pgbench.

pgbench is a benchmarking tool that is included with PostgreSQL which means it’s already installed and available inside the running Docker container.

We will need to run the benchmark twice:

  1. For JSON
  2. For JSONB

Here’s the command for running the JSON benchmark:

docker exec -it crv_postgres_benchmark pgbench -U app_user -c 8 -j 8 -T 60 -f /test/test_json.sql app_db

# or the same command split for readability:

docker exec -it \
    crv_postgres_benchmark \
    pgbench \
        -U app_user \
        -c 8 \
        -j 8 \
        -T 60 \
        -f /test/test_json.sql \
        app_dbCode language: Shell Session (shell)

Let’s break this command down.

  • docker exec -it: This command allows us to run a command inside the running Docker container. The -it flags enable interactive mode and a TTY terminal connection, which allows us to interact with the command being executed.
  • crv_postgres_benchmark: This is the name of our Postgres Docker container. We explicitly defined this container_name value inside docker-compose.yaml.
  • pgbench: This is the command that we want to run inside the container.
  • -U app_user: This flag specifies the username that pgbench will use to connect to the database.
  • -c 8: This flag sets the number of client sessions that will be used for the benchmark test to 8.
  • -j 8: This flag sets the number of threads that pgbench will use to run queries to 8.
  • -T 60: This flag sets the duration of the benchmark test to 60 seconds.
  • -f /test/test_json.sql: This flag specifies the SQL script that will be executed during the benchmark test. This script exists inside the container at that very path because of the volume setup we added to our docker-compose.yaml file.
  • app_db: This is the name of the database that pgbench will connect to and execute queries against.

tl;dr; this command runs a benchmark test on a PostgreSQL database inside a Docker container using the pgbench utility. The test runs for 60 seconds, uses 8 client sessions and 8 threads, and executes queries from an SQL script against the app_db database using the app_user username.

The command for running the JSONB benchmarks is identical, except for using -f /test/test_jsonb.sql as the script to execute inside the container.

Benchmark Results

As you have run using docker-compose up, you will need to start a separate terminal window to run the benchmark command.

When you run the benchmark script expect your PC to sound like it is about to take off.

htop output whilst running pgbench

Also you will likely see some guff in your terminal:

➜  postgres-json-jsonb-test docker exec -it \
    crv_postgres_benchmark \
    pgbench \
        -U app_user \
        -c 8 \
        -j 8 \
        -T 60 \
        -f /test/test_json.sql \
        app_db
pgbench (15.2 (Debian 15.2-1.pgdg110+1))
starting vacuum...pgbench: error: ERROR:  relation "pgbench_branches" does not exist
pgbench: detail: (ignoring this error and continuing anyway)
pgbench: error: ERROR:  relation "pgbench_tellers" does not exist
pgbench: detail: (ignoring this error and continuing anyway)
pgbench: error: ERROR:  relation "pgbench_history" does not exist
pgbench: detail: (ignoring this error and continuing anyway)
end.Code language: Shell Session (shell)

We saw above how the -T 60 means this will take 60 seconds exactly to run.

So … be patient.

JSON Output

When it completes, you should get some output like:

transaction type: /test/test_json.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 5158
number of failed transactions: 0 (0.000%)
latency average = 93.191 ms
initial connection time = 3.051 ms
tps = 85.844821 (without initial connection time)Code language: Shell Session (shell)

JSONB Output

And then we can run the variant command for JSONB.

docker exec -it \
    crv_postgres_benchmark \
    pgbench \
        -U app_user \
        -c 8 \
        -j 8 \
        -T 60 \
        -f /test/test_jsonb.sql \
        app_db

# ... guff

transaction type: /test/test_jsonb.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 4896
number of failed transactions: 0 (0.000%)
latency average = 98.178 ms
initial connection time = 4.013 ms
tps = 81.484635 (without initial connection time)Code language: Shell Session (shell)

Spec

I figured it’s probably worthwhile sharing the spec for these benchmarks.

  • AMD Ryzen 7 5800X Processor (8C/16T)
  • Crucial P1 1 TB CT1000P1SSD8 Internal Solid State Drive-up to 2000 MB/s (3D NAND, NVMe, PCIe, M.2)
  • Crucial Ballistix RGB BL2K32G32C16U4BL 3200 MHz, DDR4, DRAM, Desktop Gaming Memory Kit, 64GB (32GB x2), CL16

Note that I didn’t stop everything running, and there’s a lot of crap running all the time on my PC. Other projects etc, that I can’t easily shut down. So this was as real world as it gets. AKA, not that scientific, but good enough for this post.

OSX Spec

I also have an overspecced Macbook sat doing nothing. I bought it for a previous project and then ever since, all the paid work I do seems to involve them sending me specially locked down kit. So all I use my Macbook for is playing / learning piano.

As such it was basically idle.

Here’s the spec:

osx benchmark spec

And the results:

JSON Results

transaction type: /test/test_json.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 5207
number of failed transactions: 0 (0.000%)
latency average = 92.203 ms
initial connection time = 4.480 ms
tps = 86.765426 (without initial connection time)Code language: Shell Session (shell)

JSONB Results

transaction type: /test/test_jsonb.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 4586
number of failed transactions: 0 (0.000%)
latency average = 104.797 ms
initial connection time = 4.422 ms
tps = 76.338164 (without initial connection time)Code language: Shell Session (shell)

I was a bit surprised by this, considering the price of the Macbook at the time. But I guess it is 3 years old now, whereas my main PC used above was made in 2021.

Anyway, I also figured I should show how this compares to a real world dedicated server.

Hetzner Dedicated Server

Now, this server is as real world as it gets… because it’s running this site, right now.

It’s also running a ton of other stuff, from client stuff to personal projects, and a bunch of other things in between.

The spec, it has to be said, is pretty crappy. But it does the job.

Hetzner Dedicated Server spec

The key factor here is that it only has crappy SATA disks in there.

The other two had far faster disks.

Ready to see the difference that makes?

JSON Results

transaction type: /test/test_json.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 1332
number of failed transactions: 0 (0.000%)
latency average = 361.817 ms
initial connection time = 7.893 ms
tps = 22.110638 (without initial connection time)Code language: Shell Session (shell)

JSONB Results

transaction type: /test/test_jsonb.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 1181
number of failed transactions: 0 (0.000%)
latency average = 408.074 ms
initial connection time = 11.081 ms
tps = 19.604271 (without initial connection time)Code language: Shell Session (shell)

No surprises that SATA Disk is slooow.

Which One to Use?

When deciding between JSON and JSONB, there are a few factors to consider.

If you are working with small datasets and do not require complex querying, JSON may be sufficient.

However, if you are working with large datasets or require efficient querying of JSON data, JSONB is the way to go.

While JSONB offers better performance, it comes at the cost of storage space.

Since JSONB stores data in a compressed binary format, it requires more CPU cycles to compress and decompress the data. However as I used JSONB in the JSON test for UPDATE purposes, that probably makes this whole thing very unfair.

The point is that for small scale stuff like what I’m doing, the main thing is to have a fast disk. Beyond that, yes, JSONB is slower, but really not noticeably slower.

Of course if you have vastly bigger data sets, and vastly more op requirements, then this will be a bigger deal. All I can say is: run your own benchmarks on your actual hardware. It’s quite insightful.

So I’m not sure what I got from all this, other than an increased knowledge of things like pgbench and that I could have saved myself about 5 hours of my life by just using JSONB like the docs told me too in the first place.

Classic.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.