FerretDB: When PG Masquerades as MongoDB
MongoDB was once a revolutionary technology that liberated developers from the “schema shackles” of relational databases, enabling rapid application development. However, as time passed, MongoDB gradually drifted away from its open-source roots, leaving many open-source projects and early-stage businesses in a bind.
Here’s the thing: most MongoDB users don’t actually need MongoDB’s advanced features. What they really need is a user-friendly, open-source document database solution. PostgreSQL has already evolved into a fully-featured, high-performance document database with its mature JSON capabilities: binary JSONB storage, GIN indexing for arbitrary fields, rich JSON processing functions, JSON PATH, and JSON Schema support. But having alternative functionality is one thing - providing a direct emulation is another beast entirely.
Enter FerretDB, born to fill this gap with a mission to provide a truly open-source MongoDB alternative. It’s an fascinating project with an interesting history - it was originally named “MangoDB” but changed to FerretDB for its 1.0 release to avoid any confusion with “MongoDB” (Mango DB vs Mongo DB). FerretDB offers applications using MongoDB drivers a smooth migration path to PostgreSQL.
Its magic trick? Making PostgreSQL impersonate MongoDB. It acts as a protocol translation middleware/proxy that enables PG to speak the MongoDB Wire Protocol. The last time we saw something similar was AWS’s Babelfish, which made PostgreSQL pretend to be Microsoft SQL Server by supporting the SQL Server wire protocol.
As an optional component, FerretDB significantly enriches the PostgreSQL ecosystem. Pigsty has supported FerretDB deployment via Docker templates since version 1.x and now offers native deployment support in v2.3. The Pigsty community has formed a partnership with the FerretDB community, paving the way for deeper integration and support.
This article will walk you through the installation, deployment, and usage of FerretDB.
Configuration
Before deploying a Mongo (FerretDB) cluster, you’ll need to define it in your configuration manifest. Here’s an example that uses the default single-node pg-meta
cluster’s meta
database as FerretDB’s underlying storage:
ferret:
hosts: { 10.10.10.10: { mongo_seq: 1 } }
vars:
mongo_cluster: ferret
mongo_pgurl: 'postgres://dbuser_meta:[email protected]:5432/meta'
Here mongo_cluster
and mongo_seq
are essential identity parameters, and for FerretDB, another required parameter is mongo_pgurl
, specifying the underlying PG location.
You can use the pg-meta
cluster as the underlying storage for FerretDB, and deploy multiple FerretDB instance replicas with L2 VIP binding to achieve high availability at the FerretDB layer itself.
ferret-ha:
hosts:
10.10.10.45: { mongo_seq: 1 }
10.10.10.46: { mongo_seq: 2 }
10.10.10.47: { mongo_seq: 3 }
vars:
mongo_cluster: ferret
mongo_pgurl: 'postgres://test:[email protected]:5436/test'
vip_enabled: true
vip_vrid: 128
vip_address: 10.10.10.99
vip_interface: eth1
Management
Create Mongo Cluster
After defining the MONGO cluster in the configuration manifest, you can use the following command to complete the installation.
./mongo.yml -l ferret # Install "MongoDB/FerretDB" on the ferret group
Because FerretDB uses PostgreSQL as its underlying storage, repeating this playbook usually won’t cause any harm.
Remove Mongo Cluster
To remove the Mongo/FerretDB cluster, run the mongo.yml
playbook’s task: mongo_purge
, and use the mongo_purge
command line parameter:
./mongo.yml -e mongo_purge=true -t mongo_purge
Install MongoSH
You can use MongoSH as a client tool to access the FerretDB cluster
cat > /etc/yum.repos.d/mongo.repo <<EOF
[mongodb-org-6.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/6.0/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-6.0.asc
EOF
yum install -y mongodb-mongosh
Of course, you can also directly install the RPM package of mongosh
:
rpm -ivh https://mirrors.tuna.tsinghua.edu.cn/mongodb/yum/el7/RPMS/mongodb-mongosh-1.9.1.x86_64.rpm
Connect to FerretDB
You can use the MongoDB connection string to access FerretDB using any language’s MongoDB driver. Here’s an example using the mongosh
command line tool we installed:
mongosh 'mongodb://dbuser_meta:[email protected]:27017?authMechanism=PLAIN'mongosh 'mongodb://test:[email protected]:27017/test?authMechanism=PLAIN'
Pigsty’s managed PostgreSQL cluster uses scram-sha-256
as the default authentication method, so you must use the PLAIN
authentication method to connect to FerretDB. See FerretDB: Authentication[17] for more details.
You can also use other PostgreSQL users to access FerretDB by specifying them in the connection string:
mongosh 'mongodb://dbuser_dba:[email protected]:27017?authMechanism=PLAIN'
Quick Start
You can connect to FerretDB and pretend it’s a MongoDB cluster.
$ mongosh 'mongodb://dbuser_meta:[email protected]:27017?authMechanism=PLAIN'
MongoDB commands will be translated to SQL
commands and executed in the underlying PostgreSQL:
use test # CREATE SCHEMA test;
db.dropDatabase() # DROP SCHEMA test;
db.createCollection('posts') # CREATE TABLE posts(_data JSONB,...)
db.posts.insert({ # INSERT INTO posts VALUES(...);
title: 'Post One',body: 'Body of post one',category: 'News',tags: ['news', 'events'],
user: {name: 'John Doe',status: 'author'},date: Date()}
)
db.posts.find().limit(2).pretty() # SELECT * FROM posts LIMIT 2;
db.posts.createIndex({ title: 1 }) # CREATE INDEX ON posts(_data->>'title');
If you’re not familiar with MongoDB, here’s a quick start tutorial that’s also applicable to FerretDB: Perform CRUD Operations with MongoDB Shell[18]
If you want to generate some sample load, you can use mongosh
to execute the following simple test playbook:
cat > benchmark.js <<'EOF'
const coll = "testColl";
const numDocs = 10000;
for (let i = 0; i < numDocs; i++) { // insert
db.getCollection(coll).insert({ num: i, name: "MongoDB Benchmark Test" });
}
for (let i = 0; i < numDocs; i++) { // select
db.getCollection(coll).find({ num: i });
}
for (let i = 0; i < numDocs; i++) { // update
db.getCollection(coll).update({ num: i }, { $set: { name: "Updated" } });
}
for (let i = 0; i < numDocs; i++) { // delete
db.getCollection(coll).deleteOne({ num: i });
}
EOF
mongosh 'mongodb://dbuser_meta:[email protected]:27017?authMechanism=PLAIN' benchmark.js
You can check out the MongoDB commands supported by FerretDB, as well as some known differences for basic usage, which usually isn’t a big deal.
- FerretDB uses the same protocol error names and codes, but the exact error messages may be different in some cases.
- FerretDB does not support NUL (
\0
) characters in strings. - FerretDB does not support nested arrays.
- FerretDB converts
-0
(negative zero) to0
(positive zero). - Document restrictions:
- document keys must not contain
.
sign; - document keys must not start with
$
sign; - document fields of double type must not contain
Infinity
,-Infinity
, orNaN
values.
- document keys must not contain
- When insert command is called, insert documents must not have duplicate keys.
- Update command restrictions:
- update operations producing
Infinity
,-Infinity
, orNaN
are not supported.
- update operations producing
- Database and collection names restrictions:
- name cannot start with the reserved prefix
_ferretdb_
; - database name must not include non-latin letters;
- collection name must be valid UTF-8 characters;
- name cannot start with the reserved prefix
- FerretDB offers the same validation rules for the
scale
parameter in both thecollStats
anddbStats
commands. If an invalidscale
value is provided in thedbStats
command, the same error codes will be triggered as with thecollStats
command.
Playbook
Pigsty provides a built-in playbook: mongo.yml
, for installing a FerretDB cluster on a node.
mongo.yml
This playbook consists of the following tasks:
mongo_check
: Check mongo identity parameters•mongo_dbsu
: Create the mongod operating system user•mongo_install
: Install mongo/ferretdb RPM package•mongo_purge
: Clean up existing mongo/ferretdb cluster (default not executed)•mongo_config
: Configure mongo/ferretdbmongo_cert
: Issue mongo/ferretdb SSL certificatemongo_launch
: Start mongo/ferretdb service•mongo_register
:Register mongo/ferretdb to Prometheus monitoring
Monitoring
MONGO module provides a simple monitoring panel: Mongo Overview
Mongo Overview
Mongo Overview: Mongo/FerretDB cluster overview
This monitoring panel provides basic monitoring metrics about FerretDB, as FerretDB uses PostgreSQL as its underlying storage, so more monitoring metrics, please refer to PostgreSQL itself monitoring.
Parameters
MONGO
[24] module provides 9 related configuration parameters, as shown in the table below:
Parameter | Type | Level | Comment |
---|---|---|---|
mongo_seq |
int | I | mongo instance number, required identity parameter |
mongo_cluster |
string | C | mongo cluster name, required identity parameter |
mongo_pgurl |
pgurl | C/I | mongo/ferretdb underlying PGURL connection string, required |
mongo_ssl_enabled |
bool | C | Whether mongo/ferretdb enable SSL? Default is false |
mongo_listen |
ip | C | mongo listening address, default to listen to all addresses |
mongo_port |
port | C | mongo service port, default to use 27017 |
mongo_ssl_port |
port | C | mongo TLS listening port, default to use 27018 |
mongo_exporter_port |
port | C | mongo exporter port, default to use 9216 |
mongo_extra_vars |
string | C | MONGO server additional environment variables, default blank string |