All Products
Search
Document Center

Hologres:Accelerate JSONB queries

Last Updated:Mar 26, 2026

Column-oriented storage for JSONB lets Hologres store and compress semi-structured data with the same efficiency as structured data — cutting storage costs and significantly speeding up field-level queries. Hologres V1.3 and later support this feature for column-oriented tables.

How it works

When you enable column-oriented storage on a JSONB column, Hologres converts the column into multiple strongly typed sub-columns at the storage layer, one per JSON key. Queries that target a specific key scan only that sub-column instead of deserializing the entire JSONB value — the same efficiency gain you get from columnar storage on structured data.

Note

Column-oriented storage applies to JSONB only. Do not enable it for JSON columns.

image

Prerequisites

Before you begin, make sure that:

  • Your Hologres instance is V1.3 or later. For best performance, upgrade to V1.3.37 or later. To upgrade manually, see Instance upgrades. To request an upgrade through the support team, see Obtain online support for Hologres.

  • The target column is in a column-oriented table. Column-oriented storage for JSONB is not supported on row-oriented tables.

  • The table contains at least 1,000 rows. Column-oriented storage is triggered only at that threshold.

When column-oriented storage underperforms

Column-oriented storage does not improve — and may hurt — performance in these cases:

ScenarioWhy it underperformsWhat to do instead
Queries that return the entire JSONB column (SELECT json_data FROM tbl)Hologres must reconstruct the original JSONB value from sub-columns, generating high I/OKeep the default storage mode for columns you query wholesale
Extremely sparse data (each key appears only once across rows)All sparse fields land in an internal holo.remaining column; no sub-columns are createdFlatten sparse fields into dedicated relational columns
JSONB where the root node is an array containing different structuresMixed structures cannot be split into consistent sub-columnsNormalize the data structure before ingestion, or store as standard JSONB

Only the -> and ->> operators trigger column-oriented access. Queries using other operators fall back to full JSONB scans, which may be slower than the original storage mode.

OperatorRight operandDescription
->TEXTObtains a JSON object field based on a key
->>TEXTReturns a JSON object field as TEXT

Enable column-oriented storage

Enable for a JSONB column

-- Enable column-oriented storage for a JSONB column.
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_columnar_type = ON);
Important

After you run this statement, Hologres converts all existing data in the column to column-oriented storage during the next compaction. Compaction consumes memory. Run it during off-peak hours: Newly written data is stored in column-oriented mode immediately after compaction completes.

VACUUM <table_name>;

Enable DECIMAL type inference (V2.0.11+)

Hologres V2.0.11 and later support column-oriented storage of data of the DECIMAL type. After you enable DECIMAL type inference for a numeric field such as balance in {"balance": 123.45}, column-oriented storage is supported for that data.

Important

Enable column-oriented storage on the column before enabling DECIMAL type inference.

-- Enable DECIMAL type inference for a JSONB column.
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_decimal = ON);

Verify the storage mode

Hologres V1.3.37 and later

SELECT * FROM hologres.hg_column_options
WHERE schema_name = '<schema_name>' AND table_name = '<table_name>';
Note

In V2.0.17 and earlier, this query works only for tables in the public schema. In V2.0.18 and later, it works for all schemas.

A column with {enable_columnar_type=on} in the options field has column-oriented storage enabled.

image

Hologres V1.3.10 to V1.3.36

SELECT DISTINCT
    a.attnum as num,
    a.attname as name,
    format_type(a.atttypid, a.atttypmod) as type,
    a.attnotnull as notnull,
    com.description as comment,
    coalesce(i.indisprimary,false) as primary_key,
    def.adsrc as default,
    a.attoptions
FROM pg_attribute a
JOIN pg_class pgc ON pgc.oid = a.attrelid
LEFT JOIN pg_index i ON
    (pgc.oid = i.indrelid AND i.indkey[0] = a.attnum)
LEFT JOIN pg_description com ON
    (pgc.oid = com.objoid AND a.attnum = com.objsubid)
LEFT JOIN pg_attrdef def ON
    (a.attrelid = def.adrelid AND a.attnum = def.adnum)
WHERE a.attnum > 0 AND pgc.oid = a.attrelid
    AND pg_table_is_visible(pgc.oid)
    AND NOT a.attisdropped
    AND pgc.relname = '<table_name>'
ORDER BY a.attnum;

Check the attoptions field. A value of enable_columnar_type = ON confirms column-oriented storage is active.

Disable column-oriented storage

Disable for a JSONB column

-- Disable column-oriented storage for a JSONB column.
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_columnar_type = OFF);
Important

After you run this statement, Hologres converts all data back to standard JSONB storage during the next compaction. Run compaction during off-peak hours with VACUUM <table_name>;.

Disable DECIMAL type inference

-- Disable DECIMAL type inference for a JSONB column.
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_decimal = OFF);
Note

Disabling DECIMAL type inference triggers compaction immediately to convert DECIMAL sub-columns back to the original format.

Add bitmap indexes for point queries (V2.0+)

After column-oriented storage converts JSONB keys into typed sub-columns, you can add bitmap indexes on those sub-columns to accelerate point queries (equality comparisons). Bitmap indexes are most effective when the key appears frequently across rows and you query by exact value.

After column-oriented storage is enabled, Hologres parses these types: INT, INT[], BIGINT, BIGINT[], TEXT, TEXT[], and JSONB. Bitmap indexes are created for INT, INT[], BIGINT, BIGINT[], TEXT, and TEXT[] sub-columns.

call set_table_property('<table_name>', 'bitmap_columns', '[<columnName>{:[on|off]}[,...]]');
ParameterDescription
table_nameName of the table
columnNameName of the JSONB column
onCreates a bitmap index for the column. The column must have column-oriented storage enabled.
offRemoves the bitmap index for the column

Example

The following example walks through enabling column-oriented storage, importing data, querying, and verifying the execution plan.

  1. Create the table.

    DROP TABLE IF EXISTS user_tags;
    
    BEGIN;
    CREATE TABLE IF NOT EXISTS user_tags (
        ds   timestamptz,
        tags jsonb
    );
    COMMIT;
  2. Enable column-oriented storage on the tags column.

    ALTER TABLE user_tags ALTER COLUMN tags SET (enable_columnar_type = ON);
  3. Verify that column-oriented storage is enabled.

    SELECT * FROM hologres.hg_column_options WHERE table_name = 'user_tags';

    Expected output — the options field for tags shows {enable_columnar_type=on}:

     schema_name | table_name | column_id | column_name |       column_type        | notnull | comment | default |          options
    -------------+------------+-----------+-------------+--------------------------+---------+---------+---------+---------------------------
     public      | user_tags  |         1 | ds          | timestamp with time zone | f       |         |         |
     public      | user_tags  |         2 | tags        | jsonb                    | f       |         |         | {enable_columnar_type=on}
    (2 rows)
  4. Import data.

    INSERT INTO user_tags (ds, tags)
    SELECT
        '2022-01-01 00:00:00+08',
        ('{"id":' || i || ',"first_name":"Sig","gender":"Male"}')::jsonb
    FROM generate_series(1, 10001) i;
  5. (Optional) Flush data to disk to see the column-oriented storage effect immediately.

    VACUUM user_tags;
  6. Query data by a specific key.

    SELECT (tags -> 'first_name')::text AS first_name
    FROM user_tags
    WHERE (tags -> 'id')::int = 10;
  7. Verify that column-oriented storage is active by checking the execution plan.

    -- Enable detailed execution statistics.
    SET hg_experimental_show_execution_statistics_in_explain = ON;
    
    EXPLAIN ANALYZE
    SELECT (tags -> 'first_name')::text AS first_name
    FROM user_tags
    WHERE (tags -> 'id')::int = 10;

    columnar_access_used in the output confirms that column-oriented storage is being used.

    image

  8. Add a bitmap index on tags to speed up the point query in step 6.

    call set_table_property('user_tags', 'bitmap_columns', 'tags');
  9. Verify the bitmap index is used.

    EXPLAIN ANALYZE
    SELECT (tags -> 'first_name')::text AS first_name
    FROM user_tags
    WHERE (tags -> 'id')::int = 10;

    bitmap_used in the output confirms the bitmap index is active.

    image..png

Best practices

Write queries that use supported operators

Column-oriented storage only applies when queries use -> or ->>. For text conversion, ->> is faster than casting with ->:

-- Faster: uses ->> directly
SELECT json_data->>'name' FROM tbl;

-- Slower: converts JSONB field to text via cast
SELECT (json_data->'name')::text FROM tbl;

For checking whether a TEXT array field contains a specific value, use jsonb_to_textarray:

SELECT key FROM tbl WHERE jsonb_to_textarray(json_data->'phones') && ARRAY['123456'];

Diagnose queries that slow down after enabling column-oriented storage

If query performance drops after you enable column-oriented storage, check whether the query is scanning the entire JSONB value:

CREATE TABLE tbl (key int, json_data json);
ALTER TABLE tbl ALTER COLUMN json_data SET (enable_columnar_type = on);

EXPLAIN ANALYZE SELECT json_data FROM tbl WHERE key = 123;

If the execution plan output contains the following hint, the query returns the full JSONB column and column-oriented storage is causing overhead:

Column 'json_data' has enabled columnar jsonb, but the query scanned the entire Jsonb value

Disable column-oriented storage for that column, or rewrite the query to select only the specific keys you need.

Run compaction during off-peak hours

Both enabling and disabling column-oriented storage require compaction to convert existing data. Compaction consumes memory. To trigger it manually, run:

VACUUM <table_name>;

FAQ

Why did storage usage increase after I enabled column-oriented storage?

Column-oriented storage usually reduces storage by eliminating key names and compressing values by type. However, if the JSONB data is sparse, the number of sub-columns grows significantly — and each column adds overhead for statistics collection and index creation. If most sub-column types are TEXT, compression gains are limited. Whether storage decreases depends on your data's sparsity and type distribution.