Skip to main content
unbound mongodb at scale

Encryption at Rest and Its Performance Cost

4 min read Chapter 69 of 72

Encryption at Rest and Its Performance Cost

The Symptom

After enabling LUKS full-disk encryption to meet compliance requirements, the telemetry platform’s write throughput drops from 50,000 ops/s to 38,000 ops/s. Read latency increases by 20%.

The Cause

Encryption at rest adds CPU and I/O overhead. Every block written to disk must be encrypted, and every block read must be decrypted. The cost depends on the encryption method:

  1. LUKS (dm-crypt): The Linux kernel encrypts/decrypts at the block device layer. Every I/O operation passes through the encryption layer. Uses AES-256-XTS by default. Modern CPUs have AES-NI instructions that accelerate this, but the overhead is still measurable.

  2. Cloud-managed (AWS EBS encryption, GCP CMEK): The storage controller handles encryption in hardware. The CPU overhead is near zero, but IOPS may be slightly reduced.

  3. WiredTiger native encryption (MongoDB Enterprise): Encryption happens inside WiredTiger before writing to the filesystem. The data is encrypted in the cache and on disk.

The Benchmark

Encryption methodWrite throughputRead p99CPU overheadIOPS impact
None50,000/s12msBaselineBaseline
LUKS (AES-256-XTS, AES-NI)38,000/s14.5ms+15% CPU-5% IOPS
Cloud-managed (EBS encryption)48,000/s12.5ms< 1% CPU-2% IOPS
WiredTiger native (AES-256-CBC)42,000/s13ms+10% CPUBaseline IOPS

LUKS has the highest overhead because it encrypts at the block device layer, which means every I/O operation (including filesystem metadata, journal, and oplog) is encrypted. MongoDB’s WiredTiger compression runs before LUKS encryption, so the data is compressed then encrypted.

Cloud-managed encryption has the lowest overhead because the encryption happens in the storage controller hardware, not on the CPU.

The Fix

Option 1: Cloud-managed encryption (recommended for cloud deployments).

# AWS EBS encryption via StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: mongodb-encrypted
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: "arn:aws:kms:us-east-1:123456789:key/your-key-id"
  iops: "16000"
  throughput: "250"

No MongoDB configuration changes needed. Encryption is transparent to the application.

Option 2: LUKS with AES-NI optimization (for on-premises deployments).

# Verify AES-NI support
grep -o aes /proc/cpuinfo | head -1
# aes

# Create LUKS volume with optimal cipher
cryptsetup luksFormat --cipher aes-xts-plain64 --key-size 512 \
    --hash sha256 /dev/nvme1n1

cryptsetup luksOpen /dev/nvme1n1 mongodb-data
mkfs.xfs /dev/mapper/mongodb-data
mount -o noatime /dev/mapper/mongodb-data /data/db

Optimize LUKS for MongoDB by enabling write-back caching and increasing the number of encryption threads:

# Enable write-back mode (faster, relies on UPS for crash safety)
cryptsetup refresh --perf-no_read_workqueue --perf-no_write_workqueue \
    --allow-discards mongodb-data

# Check dm-crypt performance flags
dmsetup table mongodb-data
# 0 ... crypt aes-xts-plain64 ... no_read_workqueue no_write_workqueue

Option 3: Tune MongoDB to reduce encryption overhead.

Regardless of encryption method, reduce the number of I/O operations to minimize the encryption tax:

// FAST: Larger batch sizes reduce per-operation encryption overhead
MongoClientSettings settings = MongoClientSettings.builder()
    .applyConnectionString(new ConnectionString(uri))
    .applyToSocketSettings(builder -> builder
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
    )
    .build();

// Use larger cursors to reduce round trips (fewer encrypted I/O operations)
collection.find(filter)
    .batchSize(500)   // Default 101. Larger batches = fewer I/O operations
    .into(results);

The Proof

ConfigurationWrite throughputCPU overheadCompliance
No encryption50,000/sBaselineNot compliant
LUKS (default settings)38,000/s+15%Compliant
LUKS (optimized)44,000/s+12%Compliant
Cloud-managed (EBS)48,000/s< 1%Compliant

After LUKS optimization (no_read_workqueue, no_write_workqueue):

MetricLUKS defaultLUKS optimized
Write throughput38,000/s44,000/s
Read p9914.5ms13ms
Checkpoint duration8.5s6.2s

The Trade-off

Cloud-managed encryption is the cheapest in terms of performance but ties the deployment to a specific cloud provider. LUKS is portable (works on any Linux server) but has higher CPU overhead. WiredTiger native encryption requires MongoDB Enterprise, which has licensing costs.

The no_read_workqueue and no_write_workqueue flags in LUKS skip the dm-crypt work queues and process encryption inline. This reduces latency but increases CPU usage on the calling thread. For MongoDB, where I/O threads are already pinned to cores, inline processing is faster because it avoids the context switch to the dm-crypt work queue.

The allow-discards flag passes TRIM commands through the encryption layer. This improves SSD performance (the SSD can reclaim blocks) but leaks information about which blocks are in use. For most compliance requirements, this is acceptable because the attacker still cannot read the block contents.

For the telemetry platform, cloud-managed encryption is the choice because the deployment is on AWS. The 4% throughput reduction (50,000 to 48,000) is negligible compared to the operational simplicity of not managing encryption keys and LUKS volumes.