<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Graeme Robinson</title>
    <description>The latest articles on DEV Community by Graeme Robinson (@68wooley).</description>
    <link>https://dev.to/68wooley</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1141588%2Fa1133155-e2e9-49d9-9422-8448f7447d08.jpeg</url>
      <title>DEV Community: Graeme Robinson</title>
      <link>https://dev.to/68wooley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/68wooley"/>
    <language>en</language>
    <item>
      <title>Installing MongoDB 7.0 on the Raspberry Pi 5 Using XFS</title>
      <dc:creator>Graeme Robinson</dc:creator>
      <pubDate>Sat, 20 Jan 2024 00:57:42 +0000</pubDate>
      <link>https://dev.to/mongodb/installing-mongodb-70-on-the-raspberry-pi-5-using-xfs-3jed</link>
      <guid>https://dev.to/mongodb/installing-mongodb-70-on-the-raspberry-pi-5-using-xfs-3jed</guid>
      <description>&lt;p&gt;The Raspberry Pi is awesome, and I've been using it for various fun projects for years. In preparing for this article, I counted six of various vintages doing things around my house, from enabling my garage doors to be opened through Apple Carplay in my car as I approach home, to contributing air traffic control data from aircraft landing and departing nearby Denver International Airport to the &lt;a href="https://www.flightaware.com/live/" rel="noopener noreferrer"&gt;FlightAware project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a MongoDB fan (and — full disclosure — an employee), I've grown accustomed to the power, flexibility, and ease of use of working with document data model databases in my projects. While the excellent &lt;a href="https://www.mongodb.com/atlas" rel="noopener noreferrer"&gt;MongoDB Atlas&lt;/a&gt; cloud offering is always an option (including its big-enough-to-be-useful free tier), for some projects running on devices like Raspberry Pi, it's nice to have a local database. Until recently, though, the most up-to-date version of MongoDB that could be installed on a Raspberry Pi was version 4.4.18. Later versions — including all 5.x, 6.x, and 7.x releases — require the &lt;a href="https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/arm-architecture-armv8-2-a-evolution-and-delivery" rel="noopener noreferrer"&gt;ARMv8.2-A&lt;/a&gt; microarchitecture when running on ARM64 processors, but the processors used by Raspberry Pi on its models up to and including Raspberry Pi 4 only supported ARMv8.0. This means more recently introduced MongoDB features haven't been available on Pi deployments.&lt;/p&gt;

&lt;p&gt;With the recent introduction of the rather splendid Raspberry Pi 5, however, ARMv8.2-A is now supported, and all current MongoDB releases can once again be installed on this amazing device. Pi 5 is blazing fast compared with prior versions, too, and it's plenty capable of allowing you to run all kinds of fun projects and experimentations.&lt;/p&gt;

&lt;p&gt;In this article, I'll show you how to install MongoDB Community Edition version 7.0.x on a Pi 5 running Pi OS "bookworm" (the latest versions of MongoDB and Pi OS respectively as of January 2024). Much of this is based on an &lt;a href="https://www.mongodb.com/developer/products/mongodb/mongodb-on-raspberry-pi/" rel="noopener noreferrer"&gt;earlier article&lt;/a&gt; written by my colleague Mark Smith, but Mark was constrained by the Pi hardware available at the time he wrote his article to install MongoDB 4.4, so we'll bring that advice up to date. &lt;/p&gt;

&lt;h2&gt;
  
  
  What you will need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A Raspberry Pi 5b&lt;/strong&gt;: These come in 4GB and 8GB variants. Depending on how ambitious you're planning to be with your projects, I'd recommend an 8GB board, but I've had MongoDB up and running on older Raspberry Pis with as little as 2GB of RAM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A microSD memory card&lt;/strong&gt;: This will be the Pi's primary storage. You could probably get a working installation on a  4GB card, but that's not going to leave much room for data or logs. For this article, I'll be using a 128GB U3 card from Samsung. A good quality card from a reputable brand with high read and write speeds is worth the investment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A USB flash drive&lt;/strong&gt;: We'll be using this to temporarily boot your Raspberry Pi in order to modify the partitions on your main microSD card before we use it for the first time. You'll need a drive with around 3GB of storage for this and we will be reformatting it so make sure you back up any existing data that's on it first. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A computer to run the Raspberry Pi Imager&lt;/strong&gt;: Any machine running Windows or Mac OS will work as long as it can read your microSD card and your USB flash drive. I'll be using my main Mac laptop for this.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we know what we need, let's review the steps we're going to follow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Flash Raspberry Pi OS onto your microSD card and USB flash drive.&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; Modify the partitions on your microSD card.&lt;br&gt;
&lt;strong&gt;3.&lt;/strong&gt; Create XFS filesystems to run MongoDB on.&lt;br&gt;
&lt;strong&gt;4.&lt;/strong&gt; Install and configure MongoDB. &lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flash Pi OS onto your microSD card and USB flash drive
&lt;/h2&gt;

&lt;p&gt;Raspberry Pi makes a handy tool available for flashing an operating system image onto storage devices. You can download it from &lt;a href="https://www.raspberrypi.com/software/" rel="noopener noreferrer"&gt;the Raspberry Pi website&lt;/a&gt;. We'll use this tool to flash an operating system image onto both our microSD card and our USB flash drive&lt;/p&gt;

&lt;p&gt;The tool gives you a bunch of options as to which operating system to flash, and any of the 64-bit Pi OS or Ubuntu (desktop or server — &lt;strong&gt;not&lt;/strong&gt; core) options will work. Just be sure &lt;strong&gt;not&lt;/strong&gt; to select a 32-bit version. For this article, I'm going to use Pi OS 64-bit Lite and the instructions assume you do too, but there shouldn't be too many places where you'll need to modify any of the instructions if — for example — you want to run Ubuntu instead. Pi OS Lite does not include a desktop environment, but the image is quite a bit smaller as a result, and I'll be doing everything in a terminal over SSH anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3nsc5vyqs14pabews1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3nsc5vyqs14pabews1y.png" alt="Screenshot of the Raspberry Pi Imager application showing the Raspberry Pi OS options available for the Raspberry Pi 5" width="680" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start by flashing your microSD card.&lt;/p&gt;

&lt;p&gt;Select your microSD card as the target storage and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzqsl3gig6lhlksmj8pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzqsl3gig6lhlksmj8pk.png" alt="Screenshot of the Raspberry Pi Imager application showing a mounted SD card as the target storage device" width="680" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've selected one of the Pi OS options to flash, you'll now be asked if you want to apply any OS customizations. I highly recommend you do this. It'll allow you to preconfigure your Pi's hostname, select a username and password (and avoid the pitfall of using a default username and password), apply wireless network settings, set locale details, and enable SSH — including using public key authentication if you wish. If you don't do this now, you'll need to do all those things manually when you first boot your Pi.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7ng6o7sf090rxtlt1tv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7ng6o7sf090rxtlt1tv.png" alt="Screenshot of the Raspberry Pi Imager application showing the " width="680" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've finished configuring the OS customizations you want to apply, start the flashing process. This will take a few minutes to complete. Once it's complete, remove the card from your computer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;IMPORTANT: Do not insert the card into your Pi and boot from it yet.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repeat these steps to flash Raspberry Pi OS onto your USB flash drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modify the partitions on your microSD card
&lt;/h2&gt;

&lt;p&gt;Linux supports several different filesystems, each with its own strengths and weaknesses. It's possible, common even, to have Linux systems with a mix of partitions using different filesystems with each partition being used for a different purpose or application type. &lt;/p&gt;

&lt;p&gt;Without going into detail, the &lt;a href="https://en.wikipedia.org/wiki/XFS" rel="noopener noreferrer"&gt;&lt;code&gt;XFS&lt;/code&gt;&lt;/a&gt; filesystem is particularly well suited to database-type workloads and is MongoDB's recommended filesystem. Pi OS and Ubuntu are both based on Debian Linux and the default filesystem on Debian is &lt;code&gt;ext4&lt;/code&gt;. In reality, MongoDB will run just fine on &lt;code&gt;ext4&lt;/code&gt; for any workload you are likely to run on a Raspberry Pi, but you will get a warning message when logging in to the MongoDB shell if it is not running on &lt;code&gt;XFS&lt;/code&gt;. If, like me, warning messages bug the heck out of you, the following steps will show you how to set up &lt;code&gt;XFS&lt;/code&gt; on your Pi OS image, starting with modifying the partitions on your microSD card. While setting up &lt;code&gt;XFS&lt;/code&gt; partitions is not absolutely necessary, it's a useful exercise to go through in case you ever find yourself setting up servers for larger workloads in the future.&lt;/p&gt;

&lt;p&gt;When the Raspberry Pi Imager flashed your card, it created two partitions — a ~500MB &lt;code&gt;FAT32&lt;/code&gt; boot partition and a second ~2GB &lt;code&gt;ext4&lt;/code&gt; data partition:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffqjgq1r3jih4xurwosc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffqjgq1r3jih4xurwosc.png" alt="Screenshot of the Mac OS Disk Utility showing the partitions created on a microSD card by the Raspberry Pi Imager application." width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do nothing else, the first time you insert your microSD card into your Pi and boot from it, the &lt;code&gt;ext4&lt;/code&gt; data partition will be expanded to use up all remaining space on the card. This would be a problem for us as it would leave no space for the &lt;code&gt;XFS&lt;/code&gt; partitions we want to add.&lt;/p&gt;

&lt;p&gt;To prevent this, and before we boot our Pi from the microSD card, we're going to first boot it using the USB flash drive and then carry out the following steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Manually resize the &lt;code&gt;ext4&lt;/code&gt; data partition on our microSD card to the size we want it to be.&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; Using the remaining space on the microSD card, add two additional partitions: one for MongoDB's data files and one for its logs.&lt;/p&gt;

&lt;p&gt;By taking these steps, when the Pi first boots from the microSD card, the &lt;code&gt;ext4&lt;/code&gt; file system will be automatically expanded to fill its now larger partition, but the partition itself will not be expanded to take up any additional space on the card.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Over the years, Raspberry Pi has updated and modified the way that the first-boot partition resizing works, and there are a number of articles on the web showing methods to prevent it that are no longer applicable. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Insert your USB flash drive into your Pi, but &lt;strong&gt;&lt;em&gt;do not&lt;/em&gt;&lt;/strong&gt; insert your microSD card yet. Power on your Pi.&lt;/p&gt;

&lt;p&gt;When it first boots, your Pi will power-cycle a couple of times, but after 20 to 30 seconds, it should be available for you to SSH into (assuming you enabled SSH during the flash process). If you didn't enable SSH or for some reason you can't determine the IP address it was allocated, you may need to initially connect it to a keyboard and monitor to log on directly, enable SSH, and get its IP address. Once you are able to SSH onto the Pi, insert your microSD card into the microSD card reader and use the &lt;code&gt;lsblk&lt;/code&gt; command to inspect its disk and partitions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5Temp:~ $ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    1   7.5G  0 disk 
├─sda1        8:1    1   512M  0 part /boot/firmware
└─sda2        8:2    1     7G  0 part /
mmcblk0     179:0    0 119.4G  0 disk 
├─mmcblk0p1 179:1    0   512M  0 part 
└─mmcblk0p2 179:2    0     2G  0 part 



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output on my machine shows two devices. The partitions on the &lt;code&gt;sda&lt;/code&gt; device are already mounted at &lt;code&gt;/boot/firmware&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt;, so that's the device the Pi booted from i.e. the USB flash drive. That means the other device — &lt;code&gt;mmcblk0&lt;/code&gt; — is our microSD card and the device we want to partition. The reported size of the devices confirms this.&lt;/p&gt;

&lt;p&gt;Running as a superuser, start the &lt;code&gt;parted&lt;/code&gt; partitioning utility and select the device to partition:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5Temp:~ $ sudo parted
GNU Parted 3.4
Using /dev/sda
Welcome to GNU Parted! Type 'help' to view a list of commands.

(parted) select /dev/mmcblk0
Using /dev/mmcblk0

(parted) print                                                            
Model: SD YD4QD (sd/mmc)
Disk /dev/mmcblk0: 128GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags: 

Number  Start   End     Size    Type     File system  Flags
 1      4194kB  541MB   537MB   primary  fat32        lba
 2      541MB   2739MB  2198MB  primary  ext4   


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;My microSD card is 128GB, so I'm going to expand my &lt;code&gt;ext4&lt;/code&gt; partition to be 30GB, then add a 20GB log partition for MongoDB and use the remaining space for a MongoDB data partition. Let's start by resizing the &lt;code&gt;ext4&lt;/code&gt; partition. Note that it was listed as partition 2:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(parted) resizepart                                                       
Partition number? 2                                                       
End?  [2739MB]? 30.5GB                                                    
(parted) print                                                            
Model: SD YD4QD (sd/mmc)
Disk /dev/mmcblk0: 128GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags: 

Number  Start   End     Size    Type     File system  Flags
 1      4194kB  541MB   537MB   primary  fat32        lba
 2      541MB   30.5GB  30.0GB  primary  ext4


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we'll add the 20GB MongoDB log partition:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(parted) mkpart                                                           
Partition type?  primary/extended? primary                                
File system type?  [ext2]? xfs                                            
Start? 30.5GB                                                             
End? 50.5GB                                                               
(parted) print                                                            
Model: SD YD4QD (sd/mmc)
Disk /dev/mmcblk0: 128GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags: 

Number  Start   End     Size    Type     File system  Flags
 1      4194kB  541MB   537MB   primary  fat32        lba
 2      541MB   30.5GB  30.0GB  primary  ext4
 3      30.5GB  50.5GB  20.0GB  primary  xfs          lba


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, add the MongoDB data partition using the remaining space (~77.5GB):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(parted) mkpart                                                           
Partition type?  primary/extended? primary                                
File system type?  [ext2]? xfs                                            
Start? 50.5GB                                                             
End? 128GB                                                                
(parted) print                                                            
Model: SD YD4QD (sd/mmc)
Disk /dev/mmcblk0: 128GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags: 

Number  Start   End     Size    Type     File system  Flags
 1      4194kB  541MB   537MB   primary  fat32        lba
 2      541MB   30.5GB  30.0GB  primary  ext4
 3      30.5GB  50.5GB  20.0GB  primary  xfs          lba
 4      50.5GB  128GB   77.7GB  primary  xfs          lba


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Close &lt;code&gt;parted&lt;/code&gt; and do a final check to make sure everything looks good using &lt;code&gt;lsblk&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(parted) quit                                                             
Information: You may need to update /etc/fstab.

graeme@pi5Temp:~ $ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    1   7.5G  0 disk 
├─sda1        8:1    1   512M  0 part /boot/firmware
└─sda2        8:2    1     7G  0 part /
mmcblk0     179:0    0 119.4G  0 disk 
├─mmcblk0p1 179:1    0   512M  0 part 
├─mmcblk0p2 179:2    0  27.9G  0 part 
├─mmcblk0p3 179:3    0  18.6G  0 part 
└─mmcblk0p4 179:4    0  72.3G  0 part


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If everything looks good, you can power down your Pi, remove the USB flash drive (we won't be needing it any more) and with your microSD card still in the Pi's microSD card reader, power up the Pi again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create XFS filesystems to run MongoDB on
&lt;/h2&gt;

&lt;p&gt;Your Pi will now boot using the microSD card. As before when we booted using the USB flash drive, your Pi will power-cycle a couple of times, but after 20 to 30 seconds it should be available for you to SSH into. Note you may have to delete some entries in the ssh known_hosts file on the machine from which you are connecting to the Pi to avoid SSH connection errors due to the remote host having changed identity. On "UNIX-ish" operating systems like Mac and Linux, this is usually in a hidden directory called &lt;code&gt;.ssh&lt;/code&gt; in your home directory. On Windows, it will depend on terminal application you are using. PuTTY, for example, stores known hosts in the registry at &lt;code&gt;\HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Once you are able to SSH onto the Pi, do so and inspect its disk and partitions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
mmcblk0     179:0    0 119.4G  0 disk 
├─mmcblk0p1 179:1    0   512M  0 part /boot/firmware
├─mmcblk0p2 179:2    0  27.9G  0 part /
├─mmcblk0p3 179:3    0  18.6G  0 part 
└─mmcblk0p4 179:4    0  72.3G  0 part

graeme@pi5PIOS:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            3.8G     0  3.8G   0% /dev
tmpfs           805M  5.1M  800M   1% /run
/dev/mmcblk0p2   28G  1.7G   25G   7% /
tmpfs           4.0G     0  4.0G   0% /dev/shm
tmpfs           5.0M   48K  5.0M   1% /run/lock
/dev/mmcblk0p1  510M   63M  448M  13% /boot/firmware
tmpfs           805M     0  805M   0% /run/user/1000


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In particular, we're looking to confirm our &lt;code&gt;ext4&lt;/code&gt; filesystem has been expanded to fill its full 30GB partition, and we have the two empty partitions ready to have &lt;code&gt;XFS&lt;/code&gt; filesystems added.&lt;/p&gt;

&lt;p&gt;As everything looks good, we can add the &lt;code&gt;XFS&lt;/code&gt; filesystems. Start by making sure Pi OS is fully up-to-date by running &lt;code&gt;apt-get update&lt;/code&gt; and &lt;code&gt;apt-get upgrade&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo apt-get -y update
graeme@pi5PIOS:~ $ sudo apt-get upgrade 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we can install &lt;code&gt;XFS&lt;/code&gt; itself:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo apt-get -y install xfsprogs


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you recall the output from &lt;code&gt;lsblk&lt;/code&gt;, our two empty partitions  are &lt;code&gt;mmcblk0p3&lt;/code&gt; and &lt;code&gt;mmcblk0p4&lt;/code&gt;. To create an &lt;code&gt;XFS&lt;/code&gt; file system on each, run the following commands:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo mkfs.xfs /dev/mmcblk0p3
graeme@pi5PIOS:~ $ sudo mkfs.xfs /dev/mmcblk0p4


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we have our two partitions with &lt;code&gt;XFS&lt;/code&gt; filesystems, we need to mount them in the right locations within our directory structure. MongoDB writes logs to &lt;code&gt;/var/log/mongodb&lt;/code&gt; and data files to &lt;code&gt;/var/lib/mongodb&lt;/code&gt;, so let's go ahead and create those:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo mkdir /var/log/mongodb
graeme@pi5PIOS:~ $ sudo mkdir /var/lib/mongodb


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can now mount the filesystems. &lt;code&gt;mmcblk0p3&lt;/code&gt; is our 20GB log partition and &lt;code&gt;mmcblk0p4&lt;/code&gt; is our larger data partition, so the commands to run are:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo mount /dev/mmcblk0p3 /var/log/mongodb
graeme@pi5PIOS:~ $ sudo mount /dev/mmcblk0p4 /var/lib/mongodb


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Confirm the mounts were successful:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
mmcblk0     179:0    0 119.4G  0 disk 
├─mmcblk0p1 179:1    0   512M  0 part /boot/firmware
├─mmcblk0p2 179:2    0  27.9G  0 part /
├─mmcblk0p3 179:3    0  18.6G  0 part /var/log/mongodb
└─mmcblk0p4 179:4    0  72.3G  0 part /var/lib/mongodb


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The final step in this stage is to add entries to the &lt;code&gt;fstab&lt;/code&gt; file so that the new &lt;code&gt;XFS&lt;/code&gt; filesystems are mounted each time your Pi is booted. To do this, we'll use the &lt;code&gt;blkid&lt;/code&gt; command to get the &lt;code&gt;PARTUUID&lt;/code&gt; of the two &lt;code&gt;XFS&lt;/code&gt; partitions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo blkid /dev/mmcblk0p3
/dev/mmcblk0p3: UUID="7fee4d44-6820-4e90-861f-defd195e5205" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="de9255b2-03"
graeme@pi5PIOS:~ $ echo 'PARTUUID=de9255b2-03 /var/log/mongodb xfs defaults 1 1 ' | sudo tee -a /etc/fstab

graeme@pi5PIOS:~ $ sudo blkid /dev/mmcblk0p4
/dev/mmcblk0p4: UUID="d8a6e88b-fba9-4719-96d4-327829c5cd8f" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="de9255b2-04"
graeme@pi5PIOS:~ $ echo 'PARTUUID=de9255b2-04 /var/lib/mongodb xfs defaults 1 1 ' | sudo tee -a /etc/fstab


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Reboot your Pi and do a final check to confirm the &lt;code&gt;XFS&lt;/code&gt; file systems have been mounted as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and configure MongoDB
&lt;/h2&gt;

&lt;p&gt;With our disk partitioned and &lt;code&gt;XFS&lt;/code&gt; file systems in place, we're now ready to install MongoDB.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; simply run &lt;code&gt;sudo apt-get install mongoDB&lt;/code&gt;. Like much software, the version of MongoDB in the standard repositories for most Linux distributions is &lt;em&gt;very&lt;/em&gt; old and most likely no longer supported. The steps in this guide will show you how to install the latest version from MongoDB's own repositories.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the time of writing, there are pre-built MongoDB packages for both Debian and Ubuntu which, like Pi OS, is Debian-based. However, the Debian packages only support AMD_64 architectures, whereas the Ubuntu packages also support the ARM64 architecture used by the Raspberry Pi. As of January 2024, the latest Pi OS release is based on Debian 12 ("bookworm"), as is Ubuntu 22.04 LTS ("Jammy"), so we'll use the MongoDB packages for that version of Ubuntu. It's worth pointing out that supported platforms and versions will evolve over time, so it's always worth confirming availability in the official &lt;a href="https://www.mongodb.com/docs/manual/administration/install-on-linux/" rel="noopener noreferrer"&gt;MongoDB installation documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To get started, we need to install the MongoDB public GPG key:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

curl -fsSL https://pgp.mongodb.com/server-7.0.asc | \
   sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg \
   --dearmor


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, create an apt source list file for the Ubuntu 22.04 packages:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Reload the local package database:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt-get update


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And, finally, install MongoDB:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt-get install -y mongodb-org


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Yay — MongoDB is now installed!!! Before we can run it, though, we need to change ownership of the &lt;code&gt;/var/log/mongodb&lt;/code&gt; and &lt;code&gt;/var/lib/mongodb&lt;/code&gt; directories to a user and group created by the installation process.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo chown mongodb:mongodb /var/log/mongodb/
sudo chown mongodb:mongodb /var/lib/mongodb/


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Pi OS uses the &lt;code&gt;systemd&lt;/code&gt; init system, so to start, stop, or check the run status of MongoDB, we use the &lt;code&gt;systemctl&lt;/code&gt; command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo systemctl start mongod
graeme@pi5PIOS:~ $ sudo systemctl status mongod
graeme@pi5PIOS:~ $ sudo systemctl stop mongod


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you want MongoDB to start automatically each time your Pi boots, use the &lt;code&gt;systemctl&lt;/code&gt; command to "enable" it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo systemctl enable mongod


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Start MongoDB and use the &lt;code&gt;systemctl status&lt;/code&gt; command to confirm it is running. If everything is good, you should now be able to connect using the &lt;code&gt;monogsh&lt;/code&gt; MongoDB shell:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ mongosh
Current Mongosh Log ID: 659f7f5de9e80a30c96e4be1
Connecting to:      mongodb://127.0.0.1:27017/?directConnection=true&amp;amp;serverSelectionTimeoutMS=2000&amp;amp;appName=mongosh+2.1.1
Using MongoDB:      7.0.5
Using Mongosh:      2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/


To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.

------
   The server generated these startup warnings when booting
   2024-01-10T22:38:09.322-07:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
   2024-01-10T22:38:09.322-07:00: vm.max_map_count is too low
------

test&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So far so good, but we want to fix those two warnings that were generated. The first, about access control, is the important one, and we'll handle that in a moment, but first, let's get rid of the &lt;code&gt;max_map_count&lt;/code&gt; warning. As with running on &lt;code&gt;XFS&lt;/code&gt;, this is also probably unnecessary for any workload we're likely to deploy on a Raspberry Pi, but the MongoDB &lt;a href="https://www.mongodb.com/docs/manual/administration/production-checklist-operations/" rel="noopener noreferrer"&gt;Production Operations Checklist&lt;/a&gt; documentation recommends setting it to 102400, so let's do it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

echo 102400 | sudo tee /proc/sys/vm/max_map_count
#Persist across reboots
echo vm.max_map_count=102400 | sudo tee -a /etc/sysctl.conf


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Restart MongoDB using &lt;code&gt;sysctl restart mongod&lt;/code&gt;, then connect with &lt;code&gt;mongosh&lt;/code&gt; again and verify the &lt;code&gt;max_map_count&lt;/code&gt; warning is no longer displayed:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo systemctl restart mongod
graeme@pi5PIOS:~ $ mongosh
Current Mongosh Log ID: 65a011a4cbbd56fd63aafa0e
Connecting to:      mongodb://127.0.0.1:27017/?directConnection=true&amp;amp;serverSelectionTimeoutMS=2000&amp;amp;appName=mongosh+2.1.1
Using MongoDB:      7.0.5
Using Mongosh:      2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2024-01-11T09:04:43.178-07:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
------

test&amp;gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Great — we can now deal with the access control warning and, as we said before, this one is important. &lt;/p&gt;

&lt;p&gt;By default, MongoDB is installed to allow anyone access to the database without authentication, but only from the device on which it is running. This means that right now, we can only access it from the Raspberry Pi itself. That might be fine if all access is from applications running on the same device, as could well be the case with deployments on something like a Raspberry Pi. In most cases, though, we will want to open up network access to MongoDB, and we will certainly want to restrict access to only authenticated users.&lt;/p&gt;

&lt;p&gt;Let's start by adding a user. Using the &lt;code&gt;mongosh&lt;/code&gt; shell, run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

use admin
db.createUser( { user: "admin",
    pwd: "YOURPASSWORDGOESHERE",
    roles: [ "userAdminAnyDatabase",
             "dbAdminAnyDatabase",
             "readWriteAnyDatabase"] } )


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Replace YOURPASSWORDGOESHERE with an actual password (all the usual recommendations about password complexity and length apply). The roles granted to user &lt;code&gt;admin&lt;/code&gt; by this command give them access to all data and allows them to administer other users, so keep that password secure. &lt;/p&gt;

&lt;p&gt;Now we'll modify MongoDB's configuration file to tell it to enforce user authentication and to listen for connections on all of the Pi's network adaptors (rather than the default of listening only on it's internal loopback adaptor).&lt;/p&gt;

&lt;p&gt;Using your preferred editor, modify &lt;code&gt;/etc/mongod.conf&lt;/code&gt;. You need to change the &lt;code&gt;bindIp&lt;/code&gt; setting from 127.0.0.1 to 0.0.0.0 (keep &lt;code&gt;port&lt;/code&gt; set to 27017), and you need to uncomment the &lt;code&gt;security:&lt;/code&gt; entry and add a new value under it, &lt;code&gt;authorization: enabled&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

net:
  port: 27017
  bindIp: 0.0.0.0

security:
  authorization: enabled


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note this is a YAML file, so the indentation is important.&lt;/p&gt;

&lt;p&gt;Restart MongoDB once again and connect with &lt;code&gt;mongosh&lt;/code&gt; — now, there should be no warnings:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@pi5PIOS:~ $ sudo systemctl restart mongod
graeme@pi5PIOS:~ $ mongosh
Current Mongosh Log ID: 65a01ab52a2e9d6bb7c7a3f4
Connecting to:      mongodb://127.0.0.1:27017/?directConnection=true&amp;amp;serverSelectionTimeoutMS=2000&amp;amp;appName=mongosh+2.1.1
Using MongoDB:      7.0.5
Using Mongosh:      2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

test&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;It's worth pointing out at this point that what we've done is  open up access to MongoDB from any device that can establish a network connection to your Raspberry Pi. I'm assuming you are running on a home or office network with a firewall between you and the open internet. Pi OS itself does not have any firewall protections enabled by default. If you want to add them, &lt;a href="https://help.ubuntu.com/community/UFW" rel="noopener noreferrer"&gt;&lt;code&gt;ufw&lt;/code&gt;&lt;/a&gt; is a pretty good option. Suffice it to say, it's almost &lt;em&gt;never&lt;/em&gt; a good idea to expose a database server to the open internet, and certainly not one without authentication enabled. You will also want to enable TLS encryption if you're deploying on, or enabling access from, anything other than your home network. Check out the &lt;a href="https://www.mongodb.com/docs/manual/administration/security-checklist/" rel="noopener noreferrer"&gt;MongoDB security checklist&lt;/a&gt; for more security advice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To test network connectivity and authentication, move to another computer with &lt;code&gt;mongosh&lt;/code&gt; installed (I'm using my Mac laptop), and attempt to connect to MongoDB on your Pi. We'll do this twice: once using an anonymous login and once using the credentials for the &lt;code&gt;admin&lt;/code&gt; user we created. On both attempts, we'll attempt to run a command to list all databases and verify the anonymous connection is blocked from doing so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@M-YYV2WV29DD ~ % mongosh --host 10.0.0.191
Current Mongosh Log ID: 65a01c72db8f065eddfc3e9b
Connecting to:      mongodb://10.0.0.191:27017/?directConnection=true&amp;amp;appName=mongosh+2.1.1
Using MongoDB:      7.0.5
Using Mongosh:      2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

test&amp;gt; db.adminCommand({listDatabases: 1})
MongoServerError: Command listDatabases requires authentication


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graeme@M-YYV2WV29DD ~ % mongosh --host 10.0.0.191 -u "admin"
Enter password: ********
Current Mongosh Log ID: 65a01e554a71d545c503bc2e
Connecting to:      mongodb://&amp;lt;credentials&amp;gt;@10.0.0.191:27017/?directConnection=true&amp;amp;appName=mongosh+2.1.1
Using MongoDB:      7.0.5
Using Mongosh:      2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

test&amp;gt; db.adminCommand({listDatabases: 1})
{
  databases: [
    { name: 'admin', sizeOnDisk: Long('135168'), empty: false },
    { name: 'config', sizeOnDisk: Long('110592'), empty: false },
    { name: 'local', sizeOnDisk: Long('73728'), empty: false }
  ],
  totalSize: Long('319488'),
  totalSizeMb: Long('0'),
  ok: 1
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Congratulations&lt;/em&gt;&lt;/strong&gt; — you now have a running deployment of the latest version of MongoDB on your Raspberry Pi. Want to use &lt;a href="https://www.mongodb.com/docs/manual/core/timeseries-collections/#time-series" rel="noopener noreferrer"&gt;time-series&lt;/a&gt; and &lt;a href="https://www.mongodb.com/docs/manual/core/clustered-collections/#clustered-collections" rel="noopener noreferrer"&gt;clustered&lt;/a&gt; collections for your home IoT project? We got ya. Want to keep your data secure using state-of-the-art &lt;a href="https://www.mongodb.com/docs/manual/core/queryable-encryption/#queryable-encryption" rel="noopener noreferrer"&gt;queryable encryption&lt;/a&gt;? We got ya too. So go, have fun, create something amazing, and tell us about it in the comments.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>raspberrypi</category>
      <category>nosql</category>
    </item>
    <item>
      <title>MongoDB Design Reviews: how applying schema design best practices resulted in a 60x performance improvement</title>
      <dc:creator>Graeme Robinson</dc:creator>
      <pubDate>Tue, 29 Aug 2023 13:19:44 +0000</pubDate>
      <link>https://dev.to/mongodb/mongodb-design-reviews-how-applying-schema-design-best-practices-resulted-in-a-60x-performance-improvement-56m5</link>
      <guid>https://dev.to/mongodb/mongodb-design-reviews-how-applying-schema-design-best-practices-resulted-in-a-60x-performance-improvement-56m5</guid>
      <description>&lt;p&gt;The transition from working with legacy relational database systems to NoSQL databases such as MongoDB requires developers to change the way they model and represent data if they are to realize the full benefits of making the switch. &lt;/p&gt;

&lt;p&gt;Whilst MongoDB has sometimes been referred to — incorrectly — as “schemaless,” the reality is that schema design is every bit as important in MongoDB as it is in any database system, and the choices you make as a schema designer and data modeler will make or break the performance of your application in MongoDB as much, if not moreso, than they will in any traditional RDBMS.  &lt;/p&gt;

&lt;p&gt;As a developer advocate on the MongoDB Strategic Accounts team, I assist customer development teams who are transitioning existing workloads or creating new workloads in MongoDB, by providing tailored data modeling sessions, also known as “design reviews.” During these sessions, we review the customer’s specific workload and provide feedback, advice, and suggestions on how best to model the data in their workload for optimal performance. &lt;/p&gt;

&lt;p&gt;At the end of the design review, customers will have a framework schema design tailored to their workload based on best practice schema design patterns developed by MongoDB over the years. &lt;/p&gt;

&lt;p&gt;In this article, we discuss a specific design review and show how, through a combination of schema design and query optimizations, the session resulted in the customer seeing a 60x improvement in the performance of one of their aggregation pipelines and allowed their application to meet its SLA targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The customer portfolio activity application
&lt;/h2&gt;

&lt;p&gt;A few months ago, we received a request from a customer in the financial services industry to carry out a design review of an application they were building in &lt;a href="https://www.mongodb.com/atlas" rel="noopener noreferrer"&gt;MongoDB Atlas&lt;/a&gt;. Among other things, the application was designed to provide regional account managers with aggregated performance data for each stock in a customer’s portfolio over a requested time period, in a given region. &lt;/p&gt;

&lt;p&gt;When the customer contacted us, the &lt;a href="https://www.mongodb.com/docs/manual/core/aggregation-pipeline/" rel="noopener noreferrer"&gt;aggregation pipeline&lt;/a&gt; they had designed to generate the data was taking between 20 and 40 seconds to complete where the application SLA called for a sub two-second response time. The database design as far as this aggregation was concerned was relatively simple, consisting of only two collections. &lt;/p&gt;

&lt;p&gt;The documents in the first collection contained customer information, including the region to which the customer belonged, and an array of stock symbols with one entry for each stock in their portfolio. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "_id": "US4246774937",
  "region": "US",
  "firstname": "Jack",
  "lastname": "Bateman",
  "portfolio": [
    "NMAI",
    "CALA",
    "MNA"
  ]
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The documents in the second collection contained stock data covering a minute of trading activity, with one document being produced for each stock. The information in these documents  included the stock symbol, the volume of shares of that stock traded, the opening price, high price, low price, closing price, and start and finish timestamps for the covered minute.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "_id": {
    "$oid": "63e15e9ad0c75e43cd1831db"
  },
  "symbol": "CALA",
  "volume": 10464,
  "opening": 0.14,
  "high": 0.14,
  "low": 0.14,
  "closing": 0.14,
  "start": {
    "$date": "2023-02-06T19:54:00.000Z"
  },
  "end": {
    "$date": "2023-02-06T19:55:00.000Z"
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0051828rx9i8vhzrnl9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0051828rx9i8vhzrnl9i.png" alt="Entity relationship diagrams showing a many-to-many relationship between documents in a Customer collection and documents in the Stock Data collection. Documents in the Customer collection contain an array of stock_symbols, linking them to the corresponding documents in the Stock Data collection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The aggregation pipeline was being executed against the customer collection, with its output designed to provide the following:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“For each customer in a selected region, provide the opening price, trading volume, high price, low price, and closing price for a specified time period for each stock in that customer’s portfolio.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To provide this output, the aggregation pipeline had been defined with five stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;An initial &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#-match--aggregation-" rel="noopener noreferrer"&gt;&lt;code&gt;$match&lt;/code&gt;&lt;/a&gt; stage that selected only documents for customers in the desired region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#-unwind--aggregation-" rel="noopener noreferrer"&gt;&lt;code&gt;$unwind&lt;/code&gt;&lt;/a&gt; stage that would duplicate the selected customer documents once for each stock in the customer’s portfolio — i.e. if the customer had 10 stocks in their portfolio, this stage would create 10 documents — one for each stock — replacing the original document that had all 10 stocks listed in an array&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A $&lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#-lookup--aggregation-" rel="noopener noreferrer"&gt;lookup&lt;/a&gt; stage that for each document created by the prior &lt;code&gt;$unwind&lt;/code&gt; stage, would perform a second aggregation pipeline against the stock data collection, taking the stock symbol and date range as input and returning the aggregated data for that stock&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A second &lt;code&gt;$unwind&lt;/code&gt; stage to flatten the array created by the prior &lt;code&gt;$lookup&lt;/code&gt; stage (that, in this case, always only contained one entry) down to an embedded object&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A final &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#-group--aggregation-" rel="noopener noreferrer"&gt;&lt;code&gt;$group&lt;/code&gt;&lt;/a&gt; stage to recombine the documents for each customer back into a single document&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The output document for a customer with two stocks in their portfolio would look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "_id": "US1438229432",
  "region": "WEST",
  "firstname": "Otto",
  "lastname": "Cast",
  "portfolio": [
    "ISR",
    "CTS",
  ],
  "stockActivity": [
    {
      "opening": 0.42,
      "high": 0.42,
      "low": 0.4003,
      "closing": 0.4196,
      "volume": {
        "$numberLong": "40611"
      },
      "symbol": "ISR"
    },
    {
      "opening": 42.7,
      "high": 42.98,
      "low": 41.62,
      "closing": 42.93,
      "volume": {
        "$numberLong": "45294"
      },
      "symbol": "CTS"
    }
  ]
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;An examination of the &lt;a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.aggregate/#return-information-on-aggregation-pipeline-operation" rel="noopener noreferrer"&gt;explain plan&lt;/a&gt; for the pipeline showed that the two &lt;code&gt;$match&lt;/code&gt; stages — one in the main pipeline and one in the sub-pipeline within the &lt;code&gt;$lookup&lt;/code&gt; stage — were both correctly using the indexes set up to support the pipeline. This eliminated missing or incorrectly defined indexes — one of the most common sources of performance issues we see in MongoDB — as the source of the issues in this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assessing the workload
&lt;/h2&gt;

&lt;p&gt;Whenever we design a data model or schema for MongoDB, best practice calls for starting by understanding and quantifying the target workload. At the start of the design review, we were able to ascertain the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The application contained data on approximately 10,000 customers, evenly distributed across six regions within the United States.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each customer had, on average, 10 stocks listed in their portfolio, with the highest number of stocks for any one customer being 20.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Activity data for approximately 16,000 stocks was being tracked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approximately two million stock activity records were being generated daily. Not every stock produced an update every minute, and data was only being collected for the eight-hour US market day, Monday through Friday.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;During market hours, approximately 4,200 new stock updates were received each minute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stock activity data for the last four complete quarters, plus the current quarter to-date, was being maintained. This translated to approximately 650 million stock activity documents taking up around 35 GB of storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data more than four complete quarters old was being purged from the system, so data volumes were pretty stable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The data produced by the aggregation pipeline was being used by managers to produce end-of-day, end-of-month, and end-of-quarter reports. Data was not being requested for periods of less than a day, and the data for the current day only became available after the US markets closed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reports were being generated by managers, on average, 150 times per day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The reports were not being used to make real-time trading decisions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application was being run on a MongoDB Atlas M40 three-node cluster, and the metrics above indicated that this workload should not be excessively sized for the cluster, with the only question mark being whether, at 16GB, there was sufficient memory to maintain an adequately sized working set of data in memory to handle the pipeline requests without data swapping taking place. &lt;/p&gt;

&lt;p&gt;With an understanding of the nature and scale of the workload established, we then turned our attention to the structure of the aggregation pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assessing the aggregation pipeline
&lt;/h2&gt;

&lt;p&gt;The aggregation pipeline, as originally designed by the application development team, looked as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
  {$match: {region: "WEST"}},
  {$unwind:{path: "$portfolio"}},
  {$lookup:{
    from: "stockData",
    let: {
      symbol: "$portfolio",
      start: ISODate("2022-11-07T00:00:00.000+00:00"),
      end: ISODate("2022-11-08T00:00:00.000+00:00")
    },
    pipeline: [
      {$match:{
        $expr:{ $and: [
          {$eq: ["$symbol", "$$symbol"]},
          {$gte: ["$start", "$$start"]},
          {$lt: ["$end", "$$end"]},
        ]}
      },
      {$group:{
        _id: "$symbol",
        opening: {$first: "$opening"},
        high: {$max: "$high"},
        low: {$min: "$low"},
        closing: {$last: "$closing"},
        volume: {$sum: "$volume"}
      }},
      {$set:{
        "symbol": "$_id",
        "_id": "$$REMOVE"
      }}
    ],
    as: "stockData"
  }},
  {$unwind: {path: "$stockData"}},
  {$group:{ 
      _id: "$_id",
      region:{$first: "$region"},
      firstname:{$first: "$firstname"},
      lastname:{$first: "$lastname"},
      portfolio:{$addToSet: "$portfolio"},
      stockActivity:{$push: "$stockData"}
  }}
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On running a test query retrieving data for a one-day window of trading activity for all customers in the “WEST” region, we saw a response time of just under &lt;strong&gt;29 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There were two items in this pipeline that immediately caught our attention.&lt;/p&gt;

&lt;p&gt;Firstly, the pipeline used an &lt;code&gt;$unwind&lt;/code&gt; stage to allow the subsequent &lt;code&gt;$lookup&lt;/code&gt; stage to be run once for each stock in each customer’s portfolio. In fact, this &lt;code&gt;$unwind&lt;/code&gt;, and its subsequent reconstruction of the data in the final &lt;code&gt;$group&lt;/code&gt; stage, was unnecessary. If an array is passed to a &lt;code&gt;$lookup&lt;/code&gt; stage as the &lt;code&gt;localfield&lt;/code&gt; value, the &lt;code&gt;$lookup&lt;/code&gt; will automatically be run for each entry in the array. Refactoring the pipeline to take this approach reduced it to two stages: the initial &lt;code&gt;$match&lt;/code&gt; stage and the subsequent &lt;code&gt;$lookup&lt;/code&gt; stage. The revised pipeline looked like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[ 
 {$match:{region: "WEST"}},
 {$lookup:{
    from: "stockData",
    localField: "portfolio",
    foreignField: "symbol",
    let: {
       start: ISODate(
          "2022-11-07T00:00:00.000+00:00"
       ),
       end: ISODate(
          "2022-11-08T00:00:00.000+00:00"
       ),
    },
    pipeline: [
      {$match:{
        $expr:{ $and: [
          {$eq: ["$symbol", "$$symbol"]},
          {$gte: ["$start", "$$start"]},
          {$lt: ["$end", "$$end"]},
        ]}
      },
      {$group:{
        _id: "$symbol",
        opening: {$first: "$opening"},
        high: {$max: "$high"},
        low: {$min: "$low"},
        closing: {$last: "$closing"},
        volume: {$sum: "$volume"}
      }},
      {$set:{
        "symbol": "$_id",
        "_id": "$$REMOVE"
      }}
    ],
    as: "stockActivity",
 }}
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Eliminating the &lt;code&gt;$group&lt;/code&gt; stage in particular proved beneficial, and re-running our test query gave a response time of &lt;strong&gt;19 seconds&lt;/strong&gt;. This was a significant improvement, but still well short of the target sub two-second response time.&lt;/p&gt;

&lt;p&gt;The second issue we saw in the pipeline was the use of the &lt;code&gt;$lookup&lt;/code&gt; stage. &lt;code&gt;$lookup&lt;/code&gt; essentially carries out the equivalent of what would be a left outer join in a relational database. Joins in any database system — relational or NoSQL — are computationally expensive operations. One of the key benefits of the document model used by MongoDB is its ability to allow us to avoid joins through the use of embedding and hierarchical documents. However, in this case, the application development team had correctly identified that embedding the stock activity documents in each customer’s document would lead to excessively sized documents and huge arrays — both MongoDB anti-patterns. Data denormalization and some level of duplication to improve query performance in MongoDB is often encouraged. However, in this workload, with write operations outnumbering read operations by a considerable margin, the extent and subsequent update cost of duplicating the stock activity data into the customer documents was determined to be a poor trade-off. &lt;/p&gt;

&lt;p&gt;Although embedding the stock activity documents in the customer documents was ruled out as an approach, examining exactly what was happening with the &lt;code&gt;$lookup&lt;/code&gt; stage was revealing in terms of understanding why the pipeline was taking as long as it was to execute. For example, running the pipeline to generate data for one calendar quarter for all customers in the WEST region resulted in the following metrics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The initial &lt;code&gt;$match&lt;/code&gt; stage returned 1,725 customer documents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With an average of 10 stocks in each customer’s portfolio, the subsequent &lt;code&gt;$unwind&lt;/code&gt; stage expanded the number of documents in the pipeline to 18,214.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;$lookup&lt;/code&gt; stage was then executed once for each of those 18,214 records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each execution of the lookup stage, one calendar quarter of data for a given stock needed to be aggregated. This resulted in approximately 25,000 one-minute stock activity records needing to be aggregated during each of the 18,214 executions of the &lt;code&gt;$lookup&lt;/code&gt; sub-pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As the same stock could appear in multiple customers’ portfolios, in many cases, the &lt;code&gt;$lookup&lt;/code&gt; sub-pipeline was being executed for the same stock multiple times. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During actual execution, the MongoDB aggregation engine would be able to apply some optimizations - in particular caching results of prior runs of the &lt;code&gt;$lookup&lt;/code&gt; stage allowing them to be reused by subsequent runs supplying the same parameters - so the overall performance was not quite as high as the metrics would at first suggest, but it was still a lot of work being executed, some of which was duplicative.&lt;/p&gt;

&lt;p&gt;With this understanding, the next stage in our design review was to look to see how schema design patterns could be applied to optimize the pipeline performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying schema design patterns — the computed pattern
&lt;/h2&gt;

&lt;p&gt;The first issue we looked to address was the number of stock activity documents being aggregated by each execution of the &lt;code&gt;$lookup&lt;/code&gt; sub-pipeline. &lt;/p&gt;

&lt;p&gt;Stock activity documents were being written to the database on a minute-by-minute basis, but during our workload assessment at the start of the design review, we determined that users were never querying at anything less than per-day granularity. With this in mind, we decided to investigate if the &lt;a href="https://www.mongodb.com/developer/products/mongodb/computed-pattern/" rel="noopener noreferrer"&gt;computed design pattern&lt;/a&gt; could be applied. &lt;/p&gt;

&lt;p&gt;The computed design pattern emphasizes pre-calculating and saving commonly requested data so that the same calculation is not repeated each time the data is requested. In our case, the pipeline was repeatedly aggregating the same per-minute data into daily, monthly, quarterly, or yearly totals. So, we decided to see what impact pre-calculating those totals and storing them in a new collection, and having the &lt;code&gt;$lookup&lt;/code&gt; pipeline access those pre-calculated values, would have. &lt;/p&gt;

&lt;p&gt;To do this, we suggested adding the following processes to the application:&lt;/p&gt;

&lt;p&gt;At the end of each US trading session, the per-minute documents for each stock would be aggregated to give a “daily” document with the trading volume and starting, closing, high, and low prices for each stock. These “daily” documents would be stored in a new collection and the per-minute documents deleted from the original collection, meaning it never contained more than one day’s worth of per-minute documents.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;At the start of each month, the “daily” documents for each stock would be aggregated to give a “monthly” document for each stock. The “monthly” documents would be stored in the same new collection as the daily documents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the start of each quarter, the “monthly” documents for each stock would be aggregated to give a “quarterly” document for each stock. The “quarterly” documents would also be stored in the same new collection as the daily and monthly documents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In order to differentiate between the types of documents in the new collection, they would include a “type” field with a value of either “D”, “M”, or “Q” for “daily”, “monthly”, or “quarterly” respectively. This, along with the stock symbol and the starting date for the period covered, would form a compound _id value for each document.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data for the four prior complete quarters plus the current quarter to date would be maintained. At the start of each new quarter, data for the oldest quarter would be deleted, preventing the size of the collection and its associated indexes from growing indefinitely.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An example of the new document design would look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "_id": {
    "symbol": "MDB",
    "time": {"$date": "2022-11-06T00:00:00.000Z"},
    "type": "D"
  },
  "closing": 218.51,
  "high": 218.9599,
  "low": 216.0501,
  "opening": 218.7,
  "volume": 336998
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With these changes in place, it would be possible to form a query for any range of days, months, or quarters in the dataset. The metrics for the new collection designs were encouraging too. Tracking data for the same 16,000 stocks as before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The new collection would contain roughly 5.4 million documents at most (i.e., just before a quarter end). This compared with roughly 640 million per-minute documents in the original stock activity collection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The original stock activity collection, which would still be used to gather the per-minute updates, would only ever hold a maximum of two million documents (for the current day’s updates), rather than the 640 million documents previously.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A “worst case” query requesting data for the complete date range on the day prior to a quarter end would require 30 “daily” documents, two “monthly” documents, and four “quarterly” documents — a total of 36 documents — to be aggregated per stock. Compare this with the approximately 154,000 documents that would need to be aggregated per stock to do the same calculation using per-minute documents.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modifying the pipeline to use this new structure, it now looked as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
  {$match:{region: "WEST"}},
  {$lookup:{
    from: "stockDataEnhanced",
    localField: "portfolio",
    foreignField: "_id.symbol",
    pipeline: [
      {$match: {
        $and: [
          {"_id.type": "D"},
          {"_id.time": ISODate("2022-11-07T00:00:00.000+00:00")}
        ],
      },
      {$sort:{"_id.time": 1}},
      {$group:{
        _id: "$symbol",
        opening: {$first: "$opening"},
        high: {$max: "$high"},
        low: {$min: "$low"},
        closing: {$last: "$closing"},
        volume: {$sum: "$volume"}
      }},
      {$set:{
        symbol: "$_id",
        "_id": "$$REMOVE"
      }}
    ],
    as: "stockActivity",
  }
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Executing the revised pipeline gave a response time of &lt;strong&gt;1800 ms&lt;/strong&gt; — below our two-second target SLA! However, the design review team felt there were further improvements that could be made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying schema design patterns — the extended reference and single collection patterns
&lt;/h2&gt;

&lt;p&gt;Having resolved the problem of the large numbers of documents needing to be aggregated with each execution of the &lt;code&gt;$lookup&lt;/code&gt; stage sub-pipeline, and simultaneously reducing the overall size of the database by almost 98%, we turned our attention to the other significant issue in the original pipeline: that in any given execution, the same aggregation calculations could be carried out multiple times for any given stock.&lt;/p&gt;

&lt;p&gt;To address this, we revisited our understanding of the relationships in the data and how we wanted to represent those relationships — a step that, along with quantifying our workload and assessing its access patterns, and applying best practice schema design patterns — forms the basis of our approach to data modeling in MongoDB.&lt;/p&gt;

&lt;p&gt;In this case, we were starting our pipeline with a &lt;code&gt;$match&lt;/code&gt; stage on the customer documents to find all the customers in a given region because we needed their portfolio information and that’s where it was stored — a seemingly logical design. &lt;br&gt;
However, looking at the way the data was being accessed, if we could add and maintain a list of regions for which a given stock would need to be calculated and add that to each pre-computed stock activity document for that stock, we could then initiate our pipeline against the pre-computed stock activity collection and, importantly, only aggregate data for each required stock once. Determining which regions a stock needed to be associated with would involve calculating the set of regions to which all customers holding that stock in their portfolio belonged. &lt;/p&gt;

&lt;p&gt;Embedding the region data in the pre-computed stock activity documents like this is a variant of the extended reference schema design pattern. This pattern emphasizes embedding a subset of fields from related documents in a parent document so that all the related data can be retrieved with a single query and avoiding the use of a &lt;code&gt;$lookup&lt;/code&gt; based join. Rather than embed the entire child documents, the pattern encourages embedding only those fields needed to satisfy a query predicate or those included in the query return. This helps keep the overall size of the parent document within reasonable limits. &lt;/p&gt;

&lt;p&gt;Using the extended reference pattern comes at the cost of needing to propagate changes to child data to multiple parent documents, so the pattern is particularly useful when the referenced data does not change often. In highly normalized RDBMS designs, it is not uncommon to see joins being repeatedly performed to lookup tables that contain values that haven’t changed, in some cases, in decades.&lt;/p&gt;

&lt;p&gt;In our workload, the pattern imposed the cost of possible updates to the regions each stock was associated with whenever a customer’s portfolio changed. But as this happened &lt;em&gt;relatively&lt;/em&gt; rarely, the cost was deemed acceptable given the potential query performance improvements.&lt;/p&gt;

&lt;p&gt;Applying these changes, the pre-computed stock activity documents now looked like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "_id": {
    "symbol": "MDB",
    "time": {"$date": "2022-11-06T00:00:00.000Z"},
    "type": "D"
  },
  "closing": 218.51,
  "high": 218.9599,
  "low": 216.0501,
  "opening": 218.7,
  "regions": [
    {"region": "WEST"},
    {"region": "NORTH_EAST"},
    {"region": "CENTRAL"}
  ],
  "volume": 336998
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The next question was, if we started the pipeline from the stock activity data, how would we then tie this back to the customer data? The application team’s first thought was to have another &lt;code&gt;$lookup&lt;/code&gt; stage. But on further review, we suggested they instead utilize the polymorphic nature of MongoDB collections and store the customer documents within the same collection as the pre-computed stock activity data using a &lt;a href="https://www.mongodb.com/developer/languages/java/java-single-collection-springpart1/" rel="noopener noreferrer"&gt;single-collection&lt;/a&gt; schema design pattern.&lt;/p&gt;

&lt;p&gt;Single-collection patterns emphasize storing documents of varying types, but that are related and accessed together, within the same collection. By using a common set of attributes across all of the document types in the collection, and indexing those attributes appropriately, a single database search can retrieve all the related documents with a single database operation, saving on network round-trips and marshaling/de-marshaling overheads. &lt;/p&gt;

&lt;p&gt;In our case, we elected to add customer documents to the stock activity collection using the following document shape:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "_id": {
    "customerID": "US4246774937",
    "symbol": "NMAI",
    "type": "C"
  },
  "firstname": "Jack",
  "lastname": "Bateman",
  "portfolio": [
    "NMAI",
    "PUBM",
    "MNA"
  ],
  "regions": [
    {
      "region": "US"
    }
  ]
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The key things to note with these documents are the “symbol” and “type” fields within the compound “_id” object, and the customer’s region being moved to a regions array. This made the field names and data types consistent with the stock activity daily, monthly, and quarterly documents. Note also we added one customer document for each stock within the customer’s portfolio. This allowed effective indexing of the data in the collection at the expense of some duplication of data. However, as the customer data changed relatively infrequently, this was deemed acceptable.&lt;/p&gt;

&lt;p&gt;With these changes in place, we could now define a pipeline that both avoided repeated stock aggregation calculations, and avoided the use of an expensive &lt;code&gt;$lookup&lt;/code&gt; stage. The stages in the revised pipeline were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;$match&lt;/code&gt; stage to find all documents where the region array included the target region and either the the “_id.type” field was “C” for customer, or the combination of “_id.type” and “_id.time” indicated this was a stock activity document for the time period we were calculating. (The match stage query could be updated to include whatever combination of quarterly, monthly, and daily activity documents needed to cover any time period requested.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;$group&lt;/code&gt; stage to aggregate the stock activity data for each stock, and also build an array of customer documents for each stock. As part of the data aggregation, an array of opening and closing prices from each of the individual activity documents being aggregated was built, relying on the index on the collection to ensure these were added to each array in chronological order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;$set&lt;/code&gt; stage to replace the opening price array and closing price array built by the prior &lt;code&gt;$group&lt;/code&gt; stage with the first and last entry in each array respectively to give the overall opening and closing price for the requested time period for each stock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, an &lt;code&gt;$unwind&lt;/code&gt; and &lt;code&gt;$group&lt;/code&gt; stage combination to reorganize the data by customer rather than stock, and to shape it to our desired output design.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The revised pipeline, run against the pre-calculated stock activity collection, now looked like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
  $match: {
    $and: [
      {"regions.region": "WEST"},
      {$or:[
        {"_id.type": "customer"},
        {
          "_id.type": "day",
          "_id.time": ISODate("2023-02-07T00:00:00.000+00:00")
        }
      ]}
    ]
  }}
  {$group:{
    _id: "$_id.symbol",
    volume: {$sum: "$volume"},
    opening: {$push: "$opening"},
    high: {$max: "$high"},
    low: {$min: "$low"},
    closing: {$push: "$closing"},
    customers: {
      $addToSet: {
        $cond: {
          if: {$eq: ["$_id.type", "customer"]},
          then: "$$ROOT",
          else: "$$REMOVE"
        }
      }
    }
  }},
  {$set:{
    closing: {$last: "$closing"},
    opening: {$first: "$opening"}
  }},
  {$unwind: {path: "$customers"}},
  {$group:{
    _id: "$customers._id.customerID",
    region: {
      $first: {
        $getField: {
          field: "region",
          input: {$arrayElemAt: ["$customers.regions",0]}
        }
      }
    },
    firstname: {$first: "$customers.firstname"},
    lastname: {$first: "$customers.lastname"},
    portfolio: {$first: "$customers.portfolio"},
    stockActivity: { $addToSet: {
      symbol: "$_id",
      volume: "$volume",
      opening: "$opening",
      high: "$high",
      low: "$low",
      closing: "$closing",
    }}
  }}
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A final test execution of this version of the pipeline gave a response time of &lt;strong&gt;377 ms&lt;/strong&gt; — more than four times faster than the application target response time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As is our normal practice for design reviews, we had provided the application development team with a list of questions regarding the nature and sizing of their workload ahead of time, and they came to the session well prepared.&lt;/p&gt;

&lt;p&gt;With this preparation completed, the design review session itself lasted around an hour, during which we went through our standard data modeling process of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assessing the workload and its access patterns.&lt;/li&gt;
&lt;li&gt;Reviewing the relationships in the data.&lt;/li&gt;
&lt;li&gt;Applying best practice schema design patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of session, we had collectively managed to improve the performance of the aggregation pipeline by 60x compared with its original design, handily exceeding the application target SLA, whilst simultaneously significantly reducing the storage requirements of the application. Everyone agreed it had been a very productive session.&lt;/p&gt;

&lt;p&gt;Think your team could benefit from a design review session with data modeling experts from MongoDB? Please reach out to your account representative to find out more about booking a session with our data modeling experts, either virtually or at select &lt;a href="https://www.mongodb.com/events/mongodb-local" rel="noopener noreferrer"&gt;MongoDB .local events&lt;/a&gt; in your city!&lt;/p&gt;

&lt;p&gt;If you would like to learn more about MongoDB data modeling and aggregation pipelines, we recommend the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daniel Coupal and Ken Alger’s excellent series of blog posts on &lt;a href="https://www.mongodb.com/developer/products/mongodb/polymorphic-pattern/" rel="noopener noreferrer"&gt;MongoDB schema patterns&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Daniel Coupal and Lauren Schaefer’s equally excellent series of blog posts on &lt;a href="https://www.mongodb.com/developer/products/mongodb/schema-design-anti-pattern-massive-arrays/" rel="noopener noreferrer"&gt;MongoDB anti-patterns&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Paul Done’s ebook, &lt;a href="https://www.practical-mongodb-aggregations.com/" rel="noopener noreferrer"&gt;Practical MongoDB Aggregations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MongoDB University Course, &lt;a href="https://learn.mongodb.com/courses/m320-mongodb-data-modeling" rel="noopener noreferrer"&gt;M320 - MongoDB Data Modeling&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mongodb</category>
      <category>database</category>
      <category>nosql</category>
    </item>
  </channel>
</rss>
