
{"id":7461,"date":"2020-06-10T09:46:04","date_gmt":"2020-06-10T09:46:04","guid":{"rendered":"https:\/\/rax-stg-wp-aws.com\/blogs\/get-high-throughput-and-low-latency-with-your-nvme-enabled-dual-socket-servers\/"},"modified":"2026-11-03T19:28:41","modified_gmt":"2026-11-03T19:28:41","slug":"get-high-throughput-and-low-latency-with-your-nvme-enabled-dual-socket-servers","status":"publish","type":"blogs","link":"https:\/\/www.rackspace.com\/en-gb\/blogs\/get-high-throughput-and-low-latency-with-your-nvme-enabled-dual-socket-servers\/","title":{"rendered":"Get high throughput and low latency with your NVMe-enabled dual socket servers"},"content":{"rendered":"<p>The need for access to high-speed, low-latency storage systems has become commonplace in today&#8217;s server industry. Remember, only a few years ago, people considered SATA and SAS SSDs too expensive for everyday storage use.&nbsp;<\/p>\n<p>\nToday, storage systems commonly consist of nothing but NAND-based products. I&nbsp;remember back in 2015 reading about multimillion-dollar storage systems that&nbsp;could provide a few million Input\/Output Operations Per Second (IOPS). Here we<br \/>\nare in 2020, and I can exceed those numbers with a single system and for a few&nbsp;thousand dollars. Simply amazing.<br \/>\n&nbsp;<\/p>\n<p>Peripheral Component Interconnect (PCI)-based storage has had a mixed past with&nbsp;Fusion-IO \u00ae, providing proprietary&nbsp;systems and drivers for high-speed performance. Luckily our industry overcame&nbsp;those vendor-centric models and delivered on a standard interface for PCI storage.&nbsp;<br \/>\nNon-volatile memory Express (NVMe) is the common name for providing NAND storage&nbsp;over a PCI express interface. It has an advantage over SAS and SATA in terms of&nbsp;protocol simplicity, bandwidth, and driver stacks designed for low latency and<br \/>\nhigh throughput. NVMe storage technology has been the industry leader in server&nbsp;storage performance. In its early days, NVMe was only available in a PCI&nbsp;half-height half-length (HHHL) card. This format made it difficult to scale&nbsp;systems to leverage a lot of NVMe storage.<\/p>\n<p>\nRealistically, it was not feasible to install more than two or three NVMe devices per central processing unit (CPU). Now that U.2 (2.5 inch 15mm) form factors are&nbsp;readily available, scaling NVMe devices is as easy as scaling SAS or SATA devices.&nbsp;Because today you can easily scale your Rackspace\u00ae server to allow up to 12 NVMe&nbsp;devices, you have a new set of problems to think about. First, although these&nbsp;devices are much less expensive than they used to be, they are still relatively&nbsp;costly. How do you ensure that you are getting the most out of your investment?&nbsp;Second, because these devices connect directly to the processor&#8217;s PCI bus, how do&nbsp;you ensure that performance and latency are predictable and consistent?<\/p>\n<p>In this blog post, we explore the impact of NUMA controls in Linux\u00ae&nbsp;operating systems to determine the impact of latency and overall throughput.<\/p>\n<p>We ran two simple experiments, one with NUMA control and one without NUMA control.&nbsp;Then, we explore the comparative results for latency, bandwidth, IOPs, and deviation&nbsp;of results between runs.<\/p>\n<p><strong>The experiment<\/strong><\/p>\n<p>To better describe the experiment, let me provide an example. With NVMe,&nbsp;storage attaches directly to a Peripheral Component Interconnect Express (PCIe)&nbsp;controller. That PCIe controller is embedded into the CPU package. If your&nbsp;server has one CPU, then all PCIe access is local. This configuration means the&nbsp;process that accesses the storage runs on the CPU where the storage is located.&nbsp;If your server has more than one CPU, then there is a possibility that the process&nbsp;accessing the storage is running on a CPU that does not have the PCI controller&nbsp;with your NVMe devices attached. Let&#8217;s call this *non-local access*.<\/p>\n<p>For non-local access to function, the CPU running the process must reach across the QuickPath Interconnect (QPI) links to access the other processor&#8217;s PCI&nbsp;controller. Every time this happens, this should create some measure of latency.<br \/>\nThis experiment determines the impact of local and non-local access, as shown in&nbsp;the following images:<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/blog_article_images\/26421-body-Picture1_31.png\" alt=\"nvme pic 1\" \/><\/p>\n<p>The test setup for this experiment leverages the Rackspace R740xd platform with&nbsp;12 NVMe devices and two Intel&amp;reg; Cascade Lake -R 6242R processors. We have 12&nbsp;Intel 4610 1.6T NVMe SSDs.<\/p>\n<p>For inducing I\/O work on the drives, we leverage a flexible I\/O tester (FIO). We&nbsp;are familiar with this benchmark, which includes the capability to direct the&nbsp;storage I\/O processes at specific CPU cores. This setup works perfectly to allow&nbsp;both uncontrolled and controlled storage access.<\/p>\n<p><strong>&nbsp;The setup<\/strong><\/p>\n<p>We first ran an experiment to determine how many threads were required to push&nbsp;each SSD drive to its advertised performance limit. In our testing, we found&nbsp;that it took four threads of FIO with 64 outstanding operations to keep the&nbsp;drives completely busy and running at their advertised read limits. We then&nbsp;needed to understand the system&#8217;s architecture. It is not always obvious what&nbsp;PCI lanes are attached to which NUMA domain. For this, we used a Linux tool&nbsp;called `<em>lstopo -v<\/em>` and the output of the `<em>ls -al \/sys\/block\/<\/em>` command.<\/p>\n<p><em>`ls -al \/sys\/block\/`<\/em> showed us the PCI bus number for each NVMe device. The&nbsp;<em>`lstopo<\/em>` output showed us how those PCI bus numbers mapped to the NUMA domains,&nbsp;where each of our NVMe devices was attached.<\/p>\n<p>The following image shows our system:<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/blog_article_images\/26421-body-Picture2_27.png\" alt=\"NVME pic 2\" \/><\/p>\n<p>With the output of `lspcu`, we could quickly determine what CPU cores were ineach NUMA domain, as shown in the following image:&nbsp;<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/blog_article_images\/26421-body-Picture3_21.png\" alt=\"nvme pic 3\" \/><\/p>\n<p>With these details ironed out, we now know what CPU cores to leverage to access&nbsp;each NVMe device locally.<\/p>\n<p>Our experiment has the following two test scenarios:<\/p>\n<p><strong>Scenario 1: No NUMA vontrol<\/strong><\/p>\n<p>Run the following command ten times. It performs random read access on all<br \/>\ndrives at once and does not implement any NUMA control. It reads for a total of<br \/>\n100 seconds and uses a 30 second ramp time.<\/p>\n<p>&nbsp; &nbsp; fio &#8211;direct=1 &#8211;ioengine=libaio &#8211;runtime=100 &#8211;group_reporting<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;ramp_time=30 &#8211;time_based &#8211;refill_buffers &#8211;norandommap<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k &#8211;end_fsync=1 &#8211;rwmixread=100<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;numjobs=4 &#8211;iodepth=64 &#8211;stonewall &#8211;filename=\/dev\/nvme0n1<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k &#8211;end_fsync=1 &#8211;rwmixread=100<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;numjobs=4 &#8211;iodepth=64 &#8211;filename=\/dev\/nvme1n1 &#8211;name=fio_read<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;rw=randrw &#8211;bs=4k &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;iodepth=64 &#8211;filename=\/dev\/nvme2n1 &#8211;name=fio_read &#8211;rw=randrw<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;bs=4k &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme3n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme4n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme5n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme6n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme7n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme8n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme9n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme10n1 &#8211;name=fio_read &#8211;rw=randrw &#8211;bs=4k<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;end_fsync=1 &#8211;rwmixread=100 &#8211;numjobs=4 &#8211;iodepth=64<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &#8211;filename=\/dev\/nvme11n1 &gt;&gt;fio_output.txt<\/p>\n<p><strong>&nbsp;Scenario 2: Complete NUMA control.<\/strong><\/p>\n<p>To make this work, we needed to inject the following additional command options&nbsp;into the above script:<\/p>\n<p><em>&nbsp; &nbsp; &#8211;numa_mem_policy=local<br \/>\n&nbsp; &nbsp; &#8211;cpus_allowed= &lt;cores allowed&gt;<\/em><\/p>\n<p>This operation causes FIO to isolate memory to correct NUMA regions as well as&nbsp;to only run FIO processes where we want them to. All other command parameters&nbsp;like threads, outstanding I\/O, and run\/ramp time remain the same.<\/p>\n<p><strong>The results<\/strong><\/p>\n<p>\nHere are the results of our experiment.<\/p>\n<p>As is typical in most storage performance benchmarking, this study uses IOPS as&nbsp;the metric to determine total storage throughput. Specifically, in our tests,&nbsp;we used an IOP size of 4 kilobytes. Over the ten runs, we consistently saw more&nbsp;than 6.3 million random 4k I\/O operations with NUMA controls in place. With NUMA&nbsp;controls not leveraged, the system inconsistently ranged from a high of 6.2&nbsp;million to a low of 6.071 million IOPS. Although a single system doing over six&nbsp;million IOPs is insanely fast, no one wants to leave performance on the table.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/blog_article_images\/26421-body-Picture4_18.png\" alt=\"nvme pic4\" \/><\/p>\n<p><strong>Average latency&nbsp;<\/strong><\/p>\n<p>Outside of just monitoring for total throughput, we also measured the average&nbsp;latency of I\/O operations during the ten test runs.<\/p>\n<p>The NUMA controlled benchmark showed very flat, predictable latencies that were&nbsp;lower overall than the non-NUMA controlled benchmark.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/blog_article_images\/26421-body-Picture5_15.png\" alt=\"nvme pic5\" \/><\/p>\n<p><strong>Per-drive performance deviation&nbsp;<\/strong><\/p>\n<p>\nAnother way we wanted to look at this was in terms of per-drive consistent&nbsp;performance. For each of the ten passes, did the same drives always have a&nbsp;variance in performance? We first calculated the average IOPS for each drive&nbsp;across the ten runs.<\/p>\n<p>Then we calculated the standard deviation (stdev) for the ten runs for each drive.&nbsp;The following graph represents the stdev as a percentage of the total operations&nbsp;performed.<\/p>\n<p>Without NUMA, your performance can vary by as much as 1.4 to 1.8 percent from&nbsp;run to run. With NUMA controls in place, the performance stayed within .02&nbsp;percent from run to run. &nbsp;Now, that is consistent!<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/blog_article_images\/26421-body-Picture6_12.png\" alt=\"nvme pic 6\" \/><\/p>\n<p><strong>Conclusion&nbsp;<\/strong><\/p>\n<p>Overall, our results show that leveraging NUMA to control process access to NVMe&nbsp;devices can provide not only an increase in overall throughput but also decrease&nbsp;the variability in system storage performance.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Get high throughput and low latency with your NVMe-enabled dual socket servers<\/p>\n","protected":false},"author":2,"featured_media":7463,"menu_order":0,"comment_status":"open","ping_status":"open","template":"","format":"standard","meta":{"content-type":"","nid":"26421","content_type":"blog","created_by":"10130","authored_on":"2023-12-28T10:46:04+00:00","field_article_industries":"","tag_category":"Technical","field_blog_author_text":"by Rackspace Technology Staff","seo_description":"Discover the benefits of Non-Volatile Memory Express (NVMe) storage technology for high-speed, low-latency systems.\r\n","field_featured_image":"","insights_image":"\/wp-content\/uploads\/2026\/blog_technical_images\/26421-insights_image-general-architecture-technical-blog-5-insights.jpg","field_partners":"","rxt_platforms":"Rackspace Security Essentials","product":"","publisheddate":"2020-06-10T09:46:04","rxt_solutions":"Multicloud","opengraph_tag":"","x_tag":"","teaser_flag":"Technical Blog","seo_titile":"NVMe-Enabled Dual Socket Servers | Rackspace Technology","field_topics":"","field_slug":"get-high-throughput-and-low-latency-with-your-nvme-enabled-dual-socket-servers","footnotes":""},"categories":[178],"tags":[204],"class_list":["post-7461","blogs","type-blogs","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-technical"],"_links":{"self":[{"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/blogs\/7461","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/blogs"}],"about":[{"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/types\/blogs"}],"author":[{"embeddable":true,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/comments?post=7461"}],"version-history":[{"count":3,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/blogs\/7461\/revisions"}],"predecessor-version":[{"id":9477,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/blogs\/7461\/revisions\/9477"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/media\/7463"}],"wp:attachment":[{"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/media?parent=7461"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/categories?post=7461"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rackspace.com\/en-gb\/wp-json\/wp\/v2\/tags?post=7461"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}