DEV Community

Franck Pachot
Franck Pachot

Posted on • Edited on

DocumentDB on YugabyteDB

The DocumentDB extension, providing MongoDB compatibility for PostgreSQL, is available in preview in YugabyteDB 2026.1, with some limitations, such as the absence of secondary indexes and lack of support for ARM processors. Still, it's interesting to see how it works.

I've launched a Docker container from the image containing version 2026.1.0.0, build 118:


docker run --rm -it -p 27017:27017 -p 15433:15433 \
yugabytedb/yugabyte:latest bash

Enter fullscreen mode Exit fullscreen mode

I started one node, setting the necessary flags:


yugabyted start \
 --master_flags="allowed_preview_flags_csv=ysql_enable_documentdb,ysql_enable_documentdb=true,enable_pg_cron=true"  \
 --tserver_flags="allowed_preview_flags_csv=ysql_enable_documentdb,ysql_enable_documentdb=true,enable_pg_cron=true" \
--ui=true

Enter fullscreen mode Exit fullscreen mode

The DocumentDB extension also offers a MongoDB-compatible endpoint. However, to observe the internals, I used the PostgreSQL client:


ysqlsh -h $HOSTNAME

Enter fullscreen mode Exit fullscreen mode

From the PostgreSQL client, I used the DocumentDB API to run MongoDB-compatible commands from SQL. I imported a collection with ten thousand documents, each including a nested array of one hundred items:


create extension if not exists documentdb cascade;

select documentdb_api.drop_collection    ('db','coll1');

select documentdb_api.create_collection  ('db','coll1');
with docs(document) as (select
    json_build_object(
        '_id', n,
        'field1', n%100,
        'field2', md5(random()::text),
        'field3', md5(random()::text),
        'field4', md5(random()::text),
        'field5', md5(random()::text),
        'array', (
            select json_agg(child.id+ case when n%3=0 then 0 else random() end)
            from generate_series(1, 1e2) AS child(id)
        )
    ) from generate_series(1, 1e5) n
)
select count(documentdb_api.insert_one   ('db','coll1',
 document::text::documentdb_core.bson
)) from docs;
;

Enter fullscreen mode Exit fullscreen mode

I checked a sample of data:


set documentdb_core.bsonUseEJson to true;

\pset pager off

select document from documentdb_api_catalog.bson_aggregation_pipeline(
    'db', '{"aggregate": "coll1", "pipeline": [
      {"$limit": 2 }
    ], "cursor": {}}'::documentdb_core.bson
);

Enter fullscreen mode Exit fullscreen mode

Result:

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               document                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 { "_id" : { "$numberInt" : "78047" }, "field1" : { "$numberInt" : "47" }, "field2" : "61160c5889651c7aeb9b53c9e8c16874", "field3" : "a5643ebcf7b4cb52ce5606347a48159d", "field4" : "1920dadda82764003c8666396927d184", "field5" : "38981fb4fe2874a16e71adb91eaf80b7", "array" : [ { "$numberDouble" : "1.6680338007661099642" }, { "$numberDouble" : "2.0321660675583723688" }, { "$numberDouble" : "3.7248612825324078912" }, { "$numberDouble" : "4.6682151188376419526" }, { "$numberDouble" : "5.352836859518445678" }, { "$numberDouble" : "6.1549238121424405534" }, { "$numberDouble" : "7.5698744025959001647" }, { "$numberDouble" : "8.3627292359089526741" }, { "$numberDouble" : "9.7284070730559299989" }, { "$numberDouble" : "10.648505386329139455" }, { "$numberDouble" : "11.819375264988172702" }, { "$numberDouble" : "12.564692810410857504" }, { "$numberDouble" : "13.578234292227129743" }, { "$numberDouble" : "14.840940698922509" }, { "$numberDouble" : "15.000110832002331307" }, { "$numberDouble" : "16.358619841052682631" }, { "$numberDouble" : "17.114466577365092803" }, { "$numberDouble" : "18.453576775946736177" }, { "$numberDouble" : "19.612164960288463789" }, { "$numberDouble" : "20.763510570791428478" }, { "$numberDouble" : "21.072906780595499043" }, { "$numberDouble" : "22.302558940939842813" }, { "$numberDouble" : "23.028762426311956801" }, { "$numberDouble" : "24.977457545570718622" }, { "$numberDouble" : "25.168495224042882086" }, { "$numberDouble" : "26.683268805530829582" }, { "$numberDouble" : "27.603359814890115587" }, { "$numberDouble" : "28.878206328994565411" }, { "$numberDouble" : "29.721073180897786159" }, { "$numberDouble" : "30.626948384420241922" }, { "$numberDouble" : "31.671570586699115069" }, { "$numberDouble" : "32.662353414214038594" }, { "$numberDouble" : "33.46460769319755002" }, { "$numberDouble" : "34.940574677532538317" }, { "$numberDouble" : "35.970141769064831294" }, { "$numberDouble" : "36.179330236215683669" }, { "$numberDouble" : "37.600489143561993899" }, { "$numberDouble" : "38.84836254286827284" }, { "$numberDouble" : "39.212520619284028101" }, { "$numberDouble" : "40.552350139480068947" }, { "$numberDouble" : "41.534399323092017653" }, { "$numberDouble" : "42.675192781144495768" }, { "$numberDouble" : "43.897435440034712428" }, { "$numberDouble" : "44.643362479639151275" }, { "$numberDouble" : "45.079069764447424973" }, { "$numberDouble" : "46.571893792280704361" }, { "$numberDouble" : "47.247632193766989417" }, { "$numberDouble" : "48.490043046330811194" }, { "$numberDouble" : "49.453768298556425975" }, { "$numberDouble" : "50.918392174574123032" }, { "$numberDouble" : "51.920252310666860751" }, { "$numberDouble" : "52.939591943997093892" }, { "$numberDouble" : "53.620526333881137759" }, { "$numberDouble" : "54.692199233976516837" }, { "$numberDouble" : "55.398818854086997021" }, { "$numberDouble" : "56.650202658333142836" }, { "$numberDouble" : "57.70283083519552747" }, { "$numberDouble" : "58.48719280187031444" }, { "$numberDouble" : "59.932029859433328056" }, { "$numberDouble" : "60.435350057667704959" }, { "$numberDouble" : "61.796201961995798513" }, { "$numberDouble" : "62.883084798688862804" }, { "$numberDouble" : "63.070790792109328038" }, { "$numberDouble" : "64.16759516733826274" }, { "$numberDouble" : "65.684735624962627298" }, { "$numberDouble" : "66.406523484084644338" }, { "$numberDouble" : "67.628489973539217317" }, { "$numberDouble" : "68.548155362022797021" }, { "$numberDouble" : "69.446152335761937024" }, { "$numberDouble" : "70.850816173934148878" }, { "$numberDouble" : "71.371987666701940611" }, { "$numberDouble" : "72.231763790086574772" }, { "$numberDouble" : "73.057257769223340915" }, { "$numberDouble" : "74.248606955094231807" }, { "$numberDouble" : "75.734788957354354011" }, { "$numberDouble" : "76.261763568307117112" }, { "$numberDouble" : "77.366290387804127704" }, { "$numberDouble" : "78.090952646323614772" }, { "$numberDouble" : "79.907761062715451317" }, { "$numberDouble" : "80.213292529749651294" }, { "$numberDouble" : "81.40122970782175571" }, { "$numberDouble" : "82.397874716128853834" }, { "$numberDouble" : "83.856288865693912271" }, { "$numberDouble" : "84.836437448365771274" }, { "$numberDouble" : "85.712489576106221989" }, { "$numberDouble" : "86.344972416913108759" }, { "$numberDouble" : "87.838719804924522805" }, { "$numberDouble" : "88.736218332587981195" }, { "$numberDouble" : "89.061374463030290372" }, { "$numberDouble" : "90.181943740431705692" }, { "$numberDouble" : "91.370555458410805727" }, { "$numberDouble" : "92.540259312764803212" }, { "$numberDouble" : "93.151492898837148005" }, { "$numberDouble" : "94.45972723544899452" }, { "$numberDouble" : "95.041905493739093913" }, { "$numberDouble" : "96.099361464158789659" }, { "$numberDouble" : "97.057660017948322206" }, { "$numberDouble" : "98.420677137980504767" }, { "$numberDouble" : "99.694426199374930775" }, { "$numberDouble" : "100.28095505763631934" } ] }
 { "_id" : { "$numberInt" : "84564" }, "field1" : { "$numberInt" : "64" }, "field2" : "e14fc6847516bbae0055d5e29a8331db", "field3" : "0d91f561a9af719b173283826fff7dc9", "field4" : "7c7009c2b6b2d63ada1f3c84ee9e55dc", "field5" : "43d6dbdb0613c6ed6979b797fd693c9a", "array" : [ { "$numberInt" : "1" }, { "$numberInt" : "2" }, { "$numberInt" : "3" }, { "$numberInt" : "4" }, { "$numberInt" : "5" }, { "$numberInt" : "6" }, { "$numberInt" : "7" }, { "$numberInt" : "8" }, { "$numberInt" : "9" }, { "$numberInt" : "10" }, { "$numberInt" : "11" }, { "$numberInt" : "12" }, { "$numberInt" : "13" }, { "$numberInt" : "14" }, { "$numberInt" : "15" }, { "$numberInt" : "16" }, { "$numberInt" : "17" }, { "$numberInt" : "18" }, { "$numberInt" : "19" }, { "$numberInt" : "20" }, { "$numberInt" : "21" }, { "$numberInt" : "22" }, { "$numberInt" : "23" }, { "$numberInt" : "24" }, { "$numberInt" : "25" }, { "$numberInt" : "26" }, { "$numberInt" : "27" }, { "$numberInt" : "28" }, { "$numberInt" : "29" }, { "$numberInt" : "30" }, { "$numberInt" : "31" }, { "$numberInt" : "32" }, { "$numberInt" : "33" }, { "$numberInt" : "34" }, { "$numberInt" : "35" }, { "$numberInt" : "36" }, { "$numberInt" : "37" }, { "$numberInt" : "38" }, { "$numberInt" : "39" }, { "$numberInt" : "40" }, { "$numberInt" : "41" }, { "$numberInt" : "42" }, { "$numberInt" : "43" }, { "$numberInt" : "44" }, { "$numberInt" : "45" }, { "$numberInt" : "46" }, { "$numberInt" : "47" }, { "$numberInt" : "48" }, { "$numberInt" : "49" }, { "$numberInt" : "50" }, { "$numberInt" : "51" }, { "$numberInt" : "52" }, { "$numberInt" : "53" }, { "$numberInt" : "54" }, { "$numberInt" : "55" }, { "$numberInt" : "56" }, { "$numberInt" : "57" }, { "$numberInt" : "58" }, { "$numberInt" : "59" }, { "$numberInt" : "60" }, { "$numberInt" : "61" }, { "$numberInt" : "62" }, { "$numberInt" : "63" }, { "$numberInt" : "64" }, { "$numberInt" : "65" }, { "$numberInt" : "66" }, { "$numberInt" : "67" }, { "$numberInt" : "68" }, { "$numberInt" : "69" }, { "$numberInt" : "70" }, { "$numberInt" : "71" }, { "$numberInt" : "72" }, { "$numberInt" : "73" }, { "$numberInt" : "74" }, { "$numberInt" : "75" }, { "$numberInt" : "76" }, { "$numberInt" : "77" }, { "$numberInt" : "78" }, { "$numberInt" : "79" }, { "$numberInt" : "80" }, { "$numberInt" : "81" }, { "$numberInt" : "82" }, { "$numberInt" : "83" }, { "$numberInt" : "84" }, { "$numberInt" : "85" }, { "$numberInt" : "86" }, { "$numberInt" : "87" }, { "$numberInt" : "88" }, { "$numberInt" : "89" }, { "$numberInt" : "90" }, { "$numberInt" : "91" }, { "$numberInt" : "92" }, { "$numberInt" : "93" }, { "$numberInt" : "94" }, { "$numberInt" : "95" }, { "$numberInt" : "96" }, { "$numberInt" : "97" }, { "$numberInt" : "98" }, { "$numberInt" : "99" }, { "$numberInt" : "100" } ] }
(2 rows)

yugabyte=#
Enter fullscreen mode Exit fullscreen mode

DocumentDB stores documents as BSON, which differs from JSON, particularly in its support for additional data types. With documentdb_core.bsonUseEJson, the textual representation of documents is displayed as Extended JSON instead of hexadecimal. Extended JSON is a JSON format that preserves BSON data types in a human-readable form.

To look at the internals, I need the name of the PostgreSQL table that stores the "db.coll1" collection:


-- get the internal table name
select format('documentdb_data.documents_%s',collection_id) as tab
 from documentdb_api_catalog.collections
 where (database_name,collection_name) = ('db','coll1')
\gset
\d :tab

Enter fullscreen mode Exit fullscreen mode

Result:


yugabyte-# \gset
yugabyte=# \d :tab

                   Table "documentdb_data.documents_2"

     Column      |         Type         | Collation | Nullable | Default
-----------------+----------------------+-----------+----------+---------
 shard_key_value | bigint               |           | not null |
 object_id       | documentdb_core.bson |           | not null |
 document        | documentdb_core.bson |           | not null |

Indexes:
    "collection_pk_2" PRIMARY KEY, lsm ((shard_key_value, object_id) HASH)

Check constraints:
    "shard_key_value_check" CHECK (shard_key_value = '2'::bigint)

yugabyte=#

Enter fullscreen mode Exit fullscreen mode

There is only one index, on "object_id", which corresponds to "_id". In PostgreSQL, this index is a B-tree, while YugabyteDB uses LSM trees, based on RocksDB. The table is a single shard, in DocumentDB terms, above the PostgreSQL query layer, and the MongoDB emulation layer includes the "shard_key_value" when generating the internal SQL query. YugabyteDB further shards the data beneath the PostgreSQL layer by hashing the primary key.

I review the tablets, and there's only one:


select tablet_id, table_name, state, partition_key_start, partition_key_end
from yb_local_tablets
where ysql_schema_name||'.'||table_name = :'tab';
\gset
\setenv tablet_id :tablet_id
\! /home/yugabyte/bin/yb-admin --master_addresses $HOSTNAME:7100 list_tablet_servers $tablet_id
Enter fullscreen mode Exit fullscreen mode

Result:

yugabyte=# \gset


yugabyte=# \setenv tablet_id :tablet_id

yugabyte=# \! /home/yugabyte/bin/yb-admin --master_addresses $HOSTNAME:7100 list_tablet_servers $tablet_id

Server UUID                             RPC Host/Port           Role
65fa009d2c15448db385b1abf46e1e97        3e439d75380b:9100       LEADER

yugabyte=#
Enter fullscreen mode Exit fullscreen mode

This is also visible from the UI on http://localhost:15433/databases/tabYsql

Here is the execution plan for a point query by "_id":


yugabyte=# explain (costs off, analyze on, dist on, debug on, verbose on, summary off)
select document from documentdb_api_catalog.bson_aggregation_pipeline(
    'db', '{"aggregate": "coll1", "pipeline": [
      {"$match": { "_id": 42 , "array": 42 } }
    ], "cursor": {}}'::documentdb_core.bson
);
                                                                                                                          QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using collection_pk_2 on documentdb_data.documents_2 collection (actual time=0.805..0.808 rows=1 loops=1)
   Output: document
   Index Cond: ((collection.shard_key_value = '2'::bigint) AND (collection.object_id OPERATOR(documentdb_core.=) '{ "" : { "$numberInt" : "42" } }'::documentdb_core.bson))
   Filter: ((collection.document OPERATOR(documentdb_api_catalog.@=) '{ "_id" : { "$numberInt" : "42" } }'::documentdb_core.bson) AND (collection.document OPERATOR(documentdb_api_catalog.@=) '{ "array" : { "$numberInt" : "42" } }'::documentdb_core.bson))
   Storage Table Read Requests: 1
   Storage Table Read Execution Time: 0.605 ms
   Storage Table Read Ops: 1
   Storage Table Rows Scanned: 1
     Metric rocksdb_block_cache_hit: 5.000
     Metric rocksdb_block_cache_index_hit: 1.000
     Metric rocksdb_block_cache_filter_hit: 1.000
     Metric rocksdb_block_cache_data_hit: 1.000
     Metric rocksdb_bloom_filter_useful: 1.000
     Metric rocksdb_bloom_filter_checked: 2.000
     Metric rocksdb_number_db_seek: 1.000
     Metric rocksdb_number_db_next: 1.000
     Metric rocksdb_number_db_seek_found: 1.000
     Metric rocksdb_number_db_next_found: 1.000
     Metric rocksdb_iter_bytes_read: 2561.000
     Metric rocksdb_block_cache_multi_touch_hit: 5.000
     Metric docdb_keys_found: 1.000
     Metric ql_read_latency: sum: 98.000, count: 1.000
   Estimated Seeks: 1
   Estimated Nexts And Prevs: 1001
   Estimated Table Roundtrips: 0
   Estimated Index Roundtrips: 1
   Estimated Docdb Result Width: 1285
(27 rows)

yugabyte=#


Enter fullscreen mode Exit fullscreen mode

This is an optimal plan with a single read request to the tablet, determined by the hash value of (shard_key_value, object_id), and a single RocksDB "seek" in the LSM tree to retrieve the row, followed by a single RocksDB "next".

Because secondary indexes are not supported yet in the preview integration, it is different if I query by another field:


yugabyte=# explain (costs off, analyze on, dist on, debug on, verbose on, summary off)
select document from documentdb_api_catalog.bson_aggregation_pipeline(
    'db', '{"aggregate": "coll1", "pipeline": [
      {"$match": { "field1": 42 , "array": 42 } }
    ], "cursor": {}}'::documentdb_core.bson
);
                                                                                                                            QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using collection_pk_2 on documentdb_data.documents_2 collection (actual time=2.437..186.944 rows=334 loops=1)
   Output: document
   Index Cond: (collection.shard_key_value = '2'::bigint)
   Filter: ((collection.document OPERATOR(documentdb_api_catalog.@=) '{ "field1" : { "$numberInt" : "42" } }'::documentdb_core.bson) AND (collection.document OPERATOR(documentdb_api_catalog.@=) '{ "array" : { "$numberInt" : "42" } }'::documentdb_core.bson))
   Rows Removed by Filter: 99666
   Storage Table Read Requests: 98
   Storage Table Read Execution Time: 107.827 ms
   Storage Table Read Ops: 98
   Storage Table Rows Scanned: 100000
     Metric rocksdb_block_cache_hit: 4677.000
     Metric rocksdb_block_cache_index_hit: 196.000
     Metric rocksdb_block_cache_data_hit: 4285.000
     Metric rocksdb_number_db_seek: 98.000
     Metric rocksdb_number_db_next: 100097.000
     Metric rocksdb_number_db_seek_found: 98.000
     Metric rocksdb_number_db_next_found: 100096.000
     Metric rocksdb_iter_bytes_read: 135359786.000
     Metric rocksdb_block_cache_multi_touch_hit: 4677.000
     Metric docdb_keys_found: 100097.000
     Metric ql_read_latency: sum: 79697.000, count: 98.000
   Estimated Seeks: 98
   Estimated Nexts And Prevs: 100000
   Estimated Table Roundtrips: 0
   Estimated Index Roundtrips: 98
   Estimated Docdb Result Width: 1285
(25 rows)

Enter fullscreen mode Exit fullscreen mode

This displays an Index Scan because YugabyteDB stores rows in the primary key index. However, there is no Index Cond aside from the shard_key_value, which stays constant for all rows in this table. As a result, the scan reads the entire index, like a full index traversal from beginning to end. Logically, it resembles a Seq Scan in PostgreSQL but is physically similar to an Index Only Scan without Heap Fetches. The WHERE clause predicate isn’t included in the Index Cond. Instead, the Filter and Rows Removed by Filter show how many rows are read from storage, retrieved by the query layer, and then filtered out. In the end, all rows are scanned and fetched in 98 storage requests, given the default fetch size of 1024, with filtering happening afterward at the query layer.

The preview enables testing DocumentDB features on YugabyteDB. Since Yugabyte leverages PostgreSQL for its query layer, the extension is compatible. However, due to differences in storage, indexes must be tailored accordingly. Yugabyte has created distributed versions of GIN (inverted indexes for full-text search) and HNSW (pgvector index for similarity search) but still needs to develop indexes for the DocumentDB extension (RUM and Extended RUM). I'll monitor progress through https://github.com/yugabyte/yugabyte-db/issues/31634, which tracks this development.

Top comments (1)

Collapse
 
hari90 profile image
Hari Krishna Sunder

Version 2.31 (and upcoming 2026.2) has the DocumentDB gateway. So you can natively use mongosh, and Compass too!