This post is also available in:
This post covers the full installation of MongoDB 8.0 on Oracle Linux 8 following every best practice in the official documentation, plus a complete MongoShake setup for real-time replication between two Replica Sets. Everything here was executed and validated in a real environment — nothing is theoretical.
The setup consists of 7 VMware VMs, all running Oracle Linux 8: two 3-node Replica Sets and a dedicated MongoShake VM. We used the MovieLens dataset with over 24 million documents to validate sync under near-production conditions.
Environment Architecture
192.168.15.180 mongo-vm01 PRIMARY rs-source
192.168.15.181 mongo-vm02 SECONDARY rs-source
192.168.15.182 mongo-vm03 SECONDARY rs-source
192.168.15.183 mongo-vm04 PRIMARY rs-target
192.168.15.184 mongo-vm05 SECONDARY rs-target
192.168.15.185 mongo-vm06 SECONDARY rs-target
192.168.15.186 mongo-vm07 MongoShakeEach MongoDB VM: 2 vCPUs, 2GB RAM, 20GB disk. MongoShake VM: 1 vCPU, 1GB RAM, 10GB disk.
Part 1 — Installing MongoDB 8.0 on Oracle Linux 8
This section applies to VMs 1 through 6. The approach was to configure a single base VM and clone the rest, adjusting only the IP, hostname, and replSetName per clone via sed.
Step 0 — Configure /etc/hosts
Run on all nodes to ensure name resolution without DNS dependency:
sudo bash -c 'cat >> /etc/hosts << EOF
192.168.15.180 mongo-vm01
192.168.15.181 mongo-vm02
192.168.15.182 mongo-vm03
192.168.15.183 mongo-vm04
192.168.15.184 mongo-vm05
192.168.15.185 mongo-vm06
192.168.15.186 mongo-vm07
EOF'Step 1 — Verify the kernel (mandatory on Oracle Linux)
MongoDB only supports the Red Hat Compatible Kernel (RHCK) on Oracle Linux. The Unbreakable Enterprise Kernel (UEK), which is the OL8 default, is not supported.
Official docs: “MongoDB only supports Oracle Linux running the Red Hat Compatible Kernel (RHCK). MongoDB does not support the Unbreakable Enterprise Kernel (UEK).” Source: MongoDB Production Notes
uname -rIf running UEK:
rpm -q kernel | grep -v uek
RHCK=$(rpm -q kernel | grep -v uek | tail -1 | sed 's/kernel-//')
sudo grubby --set-default /boot/vmlinuz-${RHCK}
sudo grubby --default-kernel
sudo rebootStep 2 — Install dependencies
sudo dnf install -y curl wget net-tools chrony numactlStep 3 — Add repository and install MongoDB 8.0
sudo bash -c 'cat > /etc/yum.repos.d/mongodb-org-8.0.repo << EOF
[mongodb-org-8.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/8/mongodb-org/8.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-8.0.asc
EOF'
sudo dnf install -y mongodb-orgInstalled and validated version: mongodb-org-8.0.21
Step 4 — Configure the tuned profile for MongoDB
On Oracle Linux 8, tuned is active by default and applies its kernel settings after systemd-sysctl, which means it can override parameters set in sysctl.conf. To avoid conflicts, define kernel parameters directly in the [sysctl] section of the tuned profile.
sudo dnf install -y tuned
sudo mkdir -p /etc/tuned/mongodb-ol8
sudo bash -c 'cat > /etc/tuned/mongodb-ol8/tuned.conf << EOF
[main]
summary=Tuned profile for MongoDB on Oracle Linux 8
[base]
include=throughput-performance
[vm]
transparent_hugepages=always
[sysctl]
vm.swappiness=1
vm.dirty_ratio=15
vm.dirty_background_ratio=5
vm.zone_reclaim_mode=0
net.core.somaxconn=4096
net.ipv4.tcp_fin_timeout=30
EOF'
sudo tuned-adm profile mongodb-ol8
sudo tuned-adm active
sysctl -n vm.swappinessStep 5 — Enable Transparent Huge Pages
⚠️ Critical change in MongoDB 8.0: versions 4.0 through 7.0 required THP disabled. From MongoDB 8.0 onwards, THP must be enabled.
MongoDB 8.0 ships with a new TCMalloc using per-CPU caches, which requires THP enabled. Running MongoDB 8.0 with THP disabled wastes the primary memory performance improvement of this release.
sudo bash -c 'cat > /etc/systemd/system/enable-transparent-huge-pages.service << EOF
[Unit]
Description=Enable Transparent Huge Pages for MongoDB
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=mongod.service
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo always | tee /sys/kernel/mm/transparent_hugepage/enabled > /dev/null"
[Install]
WantedBy=basic.target
EOF'
sudo systemctl daemon-reload
sudo systemctl enable --now enable-transparent-huge-pages.service
cat /sys/kernel/mm/transparent_hugepage/enabledStep 6 — Configure ulimits
On OL8, systemd has its own limit controls that can override /etc/security/limits.d/. You need to configure both.
sudo bash -c 'cat > /etc/security/limits.d/mongodb.conf << EOF
mongod soft nproc 64000
mongod hard nproc 64000
mongod soft nofile 64000
mongod hard nofile 64000
EOF'
sudo mkdir -p /etc/systemd/system/mongod.service.d/
sudo bash -c 'cat > /etc/systemd/system/mongod.service.d/limits.conf << EOF
[Service]
LimitFNOFILE=64000
LimitNPROC=64000
EOF'
sudo systemctl daemon-reloadStep 7 — Configure I/O Scheduler
For VMs with virtual disks (virtualized SSD), the recommended scheduler is none.
DISK=$(lsblk -d -o NAME,TYPE | awk '$2=="disk"{print $1}' | head -1)
echo "Disk identified: $DISK"
echo none | sudo tee /sys/block/${DISK}/queue/scheduler
sudo bash -c "cat > /etc/udev/rules.d/60-mongodb-disk.rules << EOF
ACTION==\"add|change\", KERNEL==\"${DISK}\", ATTR{queue/scheduler}=\"none\"
EOF"
sudo udevadm control --reload-rules
sudo udevadm trigger
cat /sys/block/${DISK}/queue/schedulerSource: MongoDB Production Notes
Step 8 — NTP
MongoDB requires the time difference between Replica Set members to stay below 2 seconds.
sudo systemctl enable --now chronyd
chronyc tracking | grep "System time"Step 9 — SELinux
Do not disable SELinux. The MongoDB .rpm package automatically creates /var/lib/mongo with the correct mongod_var_lib_t SELinux context. Stick to the default paths from the package to avoid having to configure SELinux contexts manually.
sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t mongod_port_t -p tcp 27017
ls -laZ /var/lib/mongo
sudo semanage port -l | grep mongodStep 10 — Firewall
sudo firewall-cmd --permanent --add-port=27017/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports | grep 27017Step 11 — Persist the PID directory
The /run/mongodb/ directory lives in tmpfs and disappears on reboot. Persist it via tmpfiles.d.
sudo bash -c 'cat > /etc/tmpfiles.d/mongodb.conf << EOF
d /run/mongodb 0755 mongod mongod -
EOF'
sudo systemd-tmpfiles --create /etc/tmpfiles.d/mongodb.conf
ls -la /run/mongodb/Step 12 — Configure mongod.conf
Key points:
- Use
/var/lib/mongo(rpm default) — not/var/lib/mongodb - Use
/run/mongodb/mongod.pid— not/var/run/mongodb/mongod.pid cacheSizeGB: formula(RAM - 1GB) * 0.5→ for 2GB RAM = 0.5GBbindIp: always loopback + the VM’s actual IP, never0.0.0.0
sudo bash -c 'cat > /etc/mongod.conf << EOF
storage:
dbPath: /var/lib/mongo
wiredTiger:
engineConfig:
cacheSizeGB: 0.5
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
logAppend: true
logRotate: rename
net:
port: 27017
bindIp: 127.0.0.1,192.168.15.180
replication:
replSetName: "rs-source"
oplogSizeMB: 1024
processManagement:
timeZoneInfo: /usr/share/zoneinfo
pidFilePath: /run/mongodb/mongod.pid
operationProfiling:
slowOpThresholdMs: 100
EOF'After cloning VMs, adjust bindIp and replSetName with sed:
sed -i 's/192.168.15.180/192.168.15.183/' /etc/mongod.conf
sed -i 's/rs-source/rs-target/' /etc/mongod.confStep 13 — Enable keyFile authentication
Replica Sets require a keyFile for internal authentication between members.
⚠️ Order matters: create users before enabling authentication. Once auth is on and no users exist, you lose access to the instance.
openssl rand -base64 756 > /tmp/mongodb-keyfile
sudo cp /tmp/mongodb-keyfile /etc/mongodb-keyfile
sudo chown mongod:mongod /etc/mongodb-keyfile
sudo chmod 400 /etc/mongodb-keyfile
for ip in 192.168.15.181 192.168.15.182 192.168.15.183 192.168.15.184 192.168.15.185; do
scp /tmp/mongodb-keyfile root@$ip:/tmp/
ssh root@$ip "sudo cp /tmp/mongodb-keyfile /etc/mongodb-keyfile && \
sudo chown mongod:mongod /etc/mongodb-keyfile && \
sudo chmod 400 /etc/mongodb-keyfile"
doneAdd to mongod.conf on all VMs:
sudo bash -c 'cat >> /etc/mongod.conf << EOF
security:
authorization: enabled
keyFile: /etc/mongodb-keyfile
EOF'Post-reboot validation script
Run after each reboot to confirm all settings persisted:
echo "=== Kernel ===" && uname -r
echo "=== THP ===" && cat /sys/kernel/mm/transparent_hugepage/enabled
echo "=== Tuned ===" && tuned-adm active
echo "=== Swappiness ===" && sysctl -n vm.swappiness
echo "=== Scheduler ===" && cat /sys/block/sda/queue/scheduler
echo "=== NTP ===" && chronyc tracking | grep "System time"
echo "=== SELinux ===" && sestatus | grep "Current mode"
echo "=== Firewall ===" && firewall-cmd --list-ports | grep 27017
echo "=== MongoDB ===" && mongod --versionExpected output after reboot (validated in real environment):
=== Kernel ===
4.18.0-553.120.1.el8_10.x86_64
=== THP ===
[always] madvise never
=== Tuned ===
Current active profile: mongodb-ol8
=== Swappiness ===
1
=== Scheduler ===
[none] mq-deadline kyber bfq
=== NTP ===
System time : 0.000000005 seconds fast of NTP time
=== SELinux ===
Current mode: enforcing
=== Firewall ===
27017/tcp
=== MongoDB ===
db version v8.0.21Part 2 — Configuring the Replica Sets
Initialize rs-source (run only on VM01)
mongosh --host 192.168.15.180 --port 27017 <<'EOF'
rs.initiate({
_id: "rs-source",
members: [
{ _id: 0, host: "192.168.15.180:27017", priority: 2 },
{ _id: 1, host: "192.168.15.181:27017", priority: 1 },
{ _id: 2, host: "192.168.15.182:27017", priority: 1 }
]
})
EOFInitialize rs-target (run only on VM04)
mongosh --host 192.168.15.183 --port 27017 <<'EOF'
rs.initiate({
_id: "rs-target",
members: [
{ _id: 0, host: "192.168.15.183:27017", priority: 2 },
{ _id: 1, host: "192.168.15.184:27017", priority: 1 },
{ _id: 2, host: "192.168.15.185:27017", priority: 1 }
]
})
EOFCreate users — correct sequence
The right sequence is: initiate → create users → enable auth → load data.
On VM01 (SOURCE):
mongosh --host 192.168.15.180 --port 27017 <<'EOF'
use admin
db.createUser({
user: "admin",
pwd: "Admin2024!",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" },
{ role: "clusterAdmin", db: "admin" }
]
})
db.createUser({
user: "shakeuser",
pwd: "Shake2024!",
roles: [
{ role: "readAnyDatabase", db: "admin" },
{ role: "read", db: "local" },
{ role: "read", db: "config" },
{ role: "readWrite", db: "mongoshake" }
]
})
EOFOn VM04 (TARGET):
mongosh --host 192.168.15.183 --port 27017 <<'EOF'
use admin
db.createUser({
user: "admin",
pwd: "Admin2024!",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" },
{ role: "clusterAdmin", db: "admin" }
]
})
db.createUser({
user: "shakeuser",
pwd: "Shake2024!",
roles: [
{ role: "readWriteAnyDatabase", db: "admin" },
{ role: "dbAdminAnyDatabase", db: "admin" }
]
})
EOF💡 shakeuser permissions on SOURCE:
readAnyDatabasedoes not cover MongoDB’s internal databases. You must explicitly addreadto thelocalandconfigdatabases. Without this, MongoShake fails with anUnauthorizederror during full sync when trying to list collections — not to replicate them. Internal databases likeadmin,local,config, andmongoshakeare filtered out by MongoShake by default and are never replicated to the target.
Part 3 — Installing and Configuring MongoShake
Download (VM07)
mkdir -p ~/mongoshake/{conf,logs}
cd ~/mongoshake
wget "https://github.com/alibaba/MongoShake/releases/download/release-v2.8.4-20230425/mongo-shake-v2.8.4.tgz" \
-O mongo-shake-v2.8.4.tgz
tar -xzf mongo-shake-v2.8.4.tgz
ln -s ~/mongoshake/mongo-shake-v2.8.4/collector.linux ~/mongoshake/collector
~/mongoshake/collector --versionConfigure collector.conf
Start from the original collector.conf shipped with the package — it already has the correct conf.version and all valid parameters for 2.8.4:
cp ~/mongoshake/mongo-shake-v2.8.4/collector.conf ~/mongoshake/conf/Apply only the required changes:
cd ~/mongoshake/conf
sed -i 's|^mongo_urls.*|mongo_urls = mongodb://shakeuser:Shake2024!@192.168.15.180:27017,192.168.15.181:27017,192.168.15.182:27017|' collector.conf
sed -i 's|^mongo_connect_mode.*|mongo_connect_mode = secondaryPreferred|' collector.conf
sed -i 's|^sync_mode.*|sync_mode = all|' collector.conf
sed -i 's|^tunnel =.*|tunnel = direct|' collector.conf
sed -i 's|^tunnel.address.*|tunnel.address = mongodb://shakeuser:Shake2024!@192.168.15.183:27017,192.168.15.184:27017,192.168.15.185:27017|' collector.conf
sed -i 's|^log.dir.*|log.dir = /root/mongoshake/logs|' collector.conf
sed -i 's|^checkpoint.start_position.*|checkpoint.start_position = 1970-01-01T00:00:00Z|' collector.conf
sed -i 's|^filter.namespace.black.*|filter.namespace.black = config|' collector.conf
sed -i 's|full_sync.collection_exist_drop = true|full_sync.collection_exist_drop = false|' collector.confKey parameters explained
| Parameter | Value | Description |
|---|---|---|
mongo_urls | all RS members | List all nodes so MongoShake can elect the read source |
mongo_connect_mode | secondaryPreferred | Reads from secondary to avoid impacting the primary |
sync_mode | all | Full sync + incremental — switch to incr after the first full sync |
tunnel | direct | Writes directly to the target MongoDB |
checkpoint.start_position | 1970-01-01 | On first run, starts from the oldest available oplog |
filter.namespace.black | config | Skips the internal config database — prevents permission errors |
full_sync.collection_exist_drop | false | Does not drop data on target at restart — critical in production |
incr_sync.target_delay | 0 | No delay — applies oplogs immediately |
Start MongoShake
cd ~/mongoshake
nohup ./collector -conf=conf/collector.conf -verbose 1 \
> logs/stdout.log 2>&1 &
echo $! > mongoshake.pid
tail -f logs/stdout.log | grep -E "stage=full|stage=incr|progress|finish full"Normal log sequence:
[INFO] [name=default-0, stage=full, get=500000, write_success=499800, tps=72258]
[INFO] collExecutor-5 sync ns {movielens ratings} successful. db syncer-0 progress 100%
[INFO] finish full sync, start incr sync with timestamp: fullBeginTs[...], fullFinishTs[...]
[INFO] [name=default-0, stage=incr, get=14, filter=14, write_success=0, tps=0, ...]⚠️ Critical gotcha — sync_mode + collection_exist_drop
Using sync_mode = all together with full_sync.collection_exist_drop = true causes MongoShake to redo the complete full sync on every restart, ignoring the checkpoint stored on the source. In production that means hours of unnecessary reprocessing.
Correct configuration:
sync_mode = all
full_sync.collection_exist_drop = false
sync_mode = incr
full_sync.collection_exist_drop = falseWith sync_mode = incr, MongoShake always resumes from the stored checkpoint without redoing the full sync.
Monitoring
curl -s http://192.168.15.186:9101 | python3 -m json.tool
curl -s http://192.168.15.186:9100 | python3 -m json.tool
mongosh --host 192.168.15.180 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin \
--eval "db.getSiblingDB('mongoshake').ckpt_default.find().pretty()"Part 4 — Validation with a Real Dataset
We used the MovieLens 32M dataset to validate sync at real volume.
Dataset loaded on SOURCE
| Database | Collection | Documents |
|---|---|---|
| movielens | movies | 87,585 |
| movielens | links | 87,585 |
| movielens | tags | 2,000,072 |
| movielens | ratings | 24,610,000 |
| my-films-db | films | 10,000 |
| Total | ~26.8 million |
Full sync result
Full sync time for ~26.8 million documents: approximately 7 minutes. Peak throughput: 102,528 documents/second.
Incremental sync test
mongosh --host 192.168.15.180 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin \
--eval "
db.getSiblingDB('movielens').ratings.insertMany([
{ userId: 999991, movieId: 1, rating: 5.0, timestamp: new Date(), tag: 'cutover-test' },
{ userId: 999992, movieId: 2, rating: 4.5, timestamp: new Date(), tag: 'cutover-test' },
{ userId: 999993, movieId: 3, rating: 4.0, timestamp: new Date(), tag: 'cutover-test' }
]);
print('Total source: ' + db.getSiblingDB('movielens').ratings.countDocuments());
"
sleep 3
mongosh --host 192.168.15.183 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin \
--eval "
print('Total target: ' + db.getSiblingDB('movielens').ratings.countDocuments());
db.getSiblingDB('movielens').ratings.find({tag: 'cutover-test'}).toArray();
"✅ Result: 3 documents replicated in under 3 seconds.
Part 5 — Cutover Procedure
The cutover is the switch that moves the application from the source cluster to the destination cluster with minimal downtime. This approach works for both datacenter migrations and MongoDB version upgrades.
Prerequisites
tail -5 ~/mongoshake/logs/stdout.log
SOURCE=$(mongosh --host 192.168.15.180 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin --quiet \
--eval "db.getSiblingDB('movielens').ratings.countDocuments()")
TARGET=$(mongosh --host 192.168.15.183 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin --quiet \
--eval "db.getSiblingDB('movielens').ratings.countDocuments()")
echo "SOURCE: $SOURCE"
echo "TARGET: $TARGET"
if [ "$SOURCE" = "$TARGET" ]; then
echo "Safe to cut over"
else
echo "DO NOT cut over — counts differ"
fiStep 1 — Block writes on source
⚠️ This action blocks all writes until unlocked. Have the rollback steps ready before executing.
mongosh --host 192.168.15.180 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin \
--eval "db.adminCommand({ fsync: 1, lock: true })"Step 2 — Wait for MongoShake to drain the lag
watch -n 2 "tail -3 ~/mongoshake/logs/stdout.log | grep stage=incr"
Step 3 — Stop MongoShake
kill $(cat ~/mongoshake/mongoshake.pid)Step 4 — Redirect the application
From:
mongodb://user:pass@192.168.15.180:27017,192.168.15.181:27017,192.168.15.182:27017/?replicaSet=rs-sourceTo:
mongodb://user:pass@192.168.15.183:27017,192.168.15.184:27017,192.168.15.185:27017/?replicaSet=rs-targetStep 5 — Validate on the new cluster
mongosh "mongodb://admin:Admin2024!@192.168.15.183:27017,192.168.15.184:27017,192.168.15.185:27017/?replicaSet=rs-target&authSource=admin" \
--eval "
print('RS: ' + rs.status().set);
print('Primary: ' + rs.status().members.find(m => m.stateStr === 'PRIMARY').name);
print('Ratings: ' + db.getSiblingDB('movielens').ratings.countDocuments());
"Rollback — if something goes wrong
mongosh --host 192.168.15.180 --port 27017 \
-u admin -p 'Admin2024!' --authenticationDatabase admin \
--eval "db.adminCommand({ fsyncUnlock: 1 })"
cd ~/mongoshake
nohup ./collector -conf=conf/collector.conf -verbose 1 \
> logs/stdout.log 2>&1 &
echo $! > mongoshake.pidVersion Upgrade Use Case
This same approach works as a version upgrade strategy for MongoDB with minimal downtime. Compared to in-place or rolling upgrades:
- The destination cluster runs the final version and is validated with real data before the cutover happens
- Rollback is immediate — just point the application back to the source cluster
- You can jump multiple major versions without the mandatory incremental upgrade path of in-place upgrades
- Zero downtime on cutover when executed correctly
💡 Limitation: MongoShake 2.8.x supports MongoDB 5.0+ as source. For older versions (4.0 or 4.2), verify compatibility before using in production.
Consolidated Checklist
- THP behavior reversed in MongoDB 8.0 — unlike earlier versions, MongoDB 8.0 requires THP enabled
- tuned can override sysctl.conf on OL8 — since tuned applies its settings after
systemd-sysctl, define kernel parameters in the[sysctl]section of the tuned profile to avoid conflicts - systemd overrides ulimits — configure both
limits.dand the systemd service override - RHCK is mandatory — UEK is not supported by MongoDB
- Use
/var/lib/mongo(rpm default) — not/var/lib/mongodb. The default path already has the correct SELinux context /run/mongodb/needstmpfiles.d— the directory is lost on reboot without thisshakeuserneedsreadonconfig—readAnyDatabasedoes not cover internal databasesfilter.namespace.black = config— MongoShake should not attempt to replicateconfig.system.sessionssync_mode = all+collection_exist_drop = trueis dangerous — triggers a full resync on every restart- After the first full sync, switch to
sync_mode = incr— resumes from the checkpoint without reprocessing
References
- MongoDB Production Notes
- TCMalloc Performance Optimization
- Install MongoDB 8.0 on Red Hat / Oracle Linux
- UNIX ulimit Settings — MongoDB Docs
- MongoDB Operations Checklist
- MongoShake — GitHub (Alibaba)
- MongoShake FAQ
- MovieLens 32M Dataset
