﻿<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>Ayende @ Rahien</title><link>https://ayende.com/blog/</link><description>Ayende @ Rahien</description><copyright>Copyright (C) Ayende Rahien  2004 - 2021 (c) 2026</copyright><ttl>60</ttl><item><title>Voron internals: I/O costs analysis</title><description>&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/2e86650cf55b_13B93/rodentia-icons_fsguard-plugin-urgent-300px_5.png"&gt;&lt;img title="rodentia-icons_fsguard-plugin-urgent-300px" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: right; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="rodentia-icons_fsguard-plugin-urgent-300px" src="https://ayende.com/blog/Images/Open-Live-Writer/2e86650cf55b_13B93/rodentia-icons_fsguard-plugin-urgent-300px_thumb_1.png" width="300" align="right" height="290"&gt;&lt;/a&gt;I talked about the details of Voron in the previous posts, how it handles &lt;a href="https://ayende.com/blog/175075/voron-internals-the-transaction-journal-recovery?key=d500b56f0ebb44e9ba1f38ac4e81d6ff"&gt;journaling&lt;/a&gt;, &lt;a href="https://ayende.com/blog/175073/vorons-internals-mvcc-all-the-moving-parts?key=3860fe89441d4b6484c8991fcf6293fe"&gt;MVCC&lt;/a&gt; and &lt;a href="https://ayende.com/blog/175074/voron-internals-cleaning-up-scratch-buffers?key=e246bcc0c6424b6c932428d5a44a9a17"&gt;cleaning up after itself&lt;/a&gt;. In this post, I want to focus on another aspect that needs to be considered, the various costs of running&amp;nbsp; Voron on production systems. In particular, the competing I/O requirements. &lt;/p&gt; &lt;p&gt;So what do we have with Voron?&lt;/p&gt; &lt;ul&gt; &lt;li&gt;A (potentially very large) memory mapped data file. Buffered writes and fsync once every 1 minute / 2GB.&lt;/li&gt; &lt;li&gt;Scratch files (small memory mapped files) marked as temporary and delete on close.&lt;/li&gt; &lt;li&gt;Journal files requiring durable writes.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;In terms of priorities, we want to give high priority to the journal files, then to writing to the data file (so it will happen all the time, not just when we call fsync). Scratch files should only be written to disk under memory pressure, and we should strive to avoid that if possible.&lt;/p&gt; &lt;p&gt;On both Windows and Linux, there are ways to ask the system to start flushing the data to disk (Windows uses FlushViewOfFile, Linux uses sync_file_range), but in practice, when we flush the data to disk we need to also ensure durability, so we call FlushViewOfFile + FlushFileBuffers on Windows and msync(MS_SYNC) on Linux to ensure that. Technically speaking, we could do this in two stages, allowing the system some time to do this lazily, then calling FlushFileBuffers / fsync, but we haven’t found that to be advantageous in terms of complexity, and sync_file_range documentation is scary. &lt;/p&gt; &lt;p&gt;Another aspect that we need to consider is the fact that we are not along out there. A typical RavenDB database will have multiple Voron instances running, and a typical RavenDB server will have multiple RavenDB databases running. So we are talking about typically having dozens or more Voron instances in a single process. We need to avoid a conflict between all of those instance, each of which is trying to make use of all the system resources by itself. This kind of disharmony can kill the performance of the server, all the while giving the best performance in any benchmark where you are running a single instance.&lt;/p&gt; &lt;p&gt;We solved this by having a single actor responsible for scheduling the flushing of all the Voron instances inside a process. It accept flush requests and make sure that we aren’t loading the I/O system too much. This means that we might actually defer flushing to disk under load, but in practice, reducing the I/O competition is going to improve throughput anyway, so that is likely to be better in the end. At the same time, we want to take advantage of the parallelism inherit in many high end systems (RAID, cloud, etc) which can handle a lot of IOPS at the same time. So the policy is to give a certain number of Voron instance the chance to run in parallel, with adjustments depending on the current I/O load on the system.&lt;/p&gt; &lt;p&gt;Journal writes, however, happen immediately, have high priority and should take precedent over data file writes, because they have immediate impact on the system.&lt;/p&gt; &lt;p&gt;We are also experimenting with using the operation system I/O priorities, but that is a bit hard, because most of those are about &lt;em&gt;reducing&lt;/em&gt; the I/O priorities. Which we sort of want, but not that much. &lt;/p&gt;</description><link>https://ayende.com/blog/175076/voron-internals-i-o-costs-analysis?Key=c71652cf-c439-44f9-b746-c5f7cdeefe09</link><guid>https://ayende.com/blog/175076/voron-internals-i-o-costs-analysis?Key=c71652cf-c439-44f9-b746-c5f7cdeefe09</guid><pubDate>Wed, 31 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Voron internals: The transaction journal &amp; recovery</title><description>&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Voron-internals-Recovery-after-a-crash_E0F1/imagebot_2.png"&gt;&lt;img title="imagebot" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: right; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="imagebot" src="https://ayende.com/blog/Images/Open-Live-Writer/Voron-internals-Recovery-after-a-crash_E0F1/imagebot_thumb.png" width="549" align="right" height="279"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;In &lt;a href="https://ayende.com/blog/175074/voron-internals-cleaning-up-scratch-buffers?key=e246bcc0c6424b6c932428d5a44a9a17"&gt;the previous post&lt;/a&gt;, I talked about the usage of scratch files to enable MVCC and the challenges that this entails. In this post, I want to talk about the role the transaction journal files play in all of this. I talked a &lt;em&gt;lot&lt;/em&gt; &lt;a href="https://ayende.com/blog/174753/fast-transaction-log-linux"&gt;about&lt;/a&gt; &lt;a href="https://ayende.com/blog/174785/fast-transaction-log-windows"&gt;how&lt;/a&gt; to &lt;a href="https://ayende.com/blog/174916/the-guts-n-glory-of-database-internals-what-goes-inside-the-transaction-journal?key=9f2e9fc51b95457eaa6029d82dba9aba"&gt;ensure&lt;/a&gt; that &lt;a href="https://ayende.com/blog/174945/the-guts-n-glory-of-database-internals-merging-transactions?key=81013acb67454013a5770884cba4159e"&gt;transaction journals&lt;/a&gt; are fast, what goes into them, etc. But this post is how&amp;nbsp; they are used inside Voron.&lt;/p&gt; &lt;p&gt;The way Voron stores data inside the transaction journal is actually quite simple. We have a transaction header, which contains quite a bit of interesting information, and then we have all the pages that were modified in this transaction, compressed.&lt;/p&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Voron-internals-Recovery-after-a-crash_E0F1/image_2.png"&gt;&lt;img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; margin: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Voron-internals-Recovery-after-a-crash_E0F1/image_thumb.png" width="858" height="182"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;The fact that we are compressing pages can save on a lot of the amount of I/O we write. But the key aspect here is that a transaction is considered committed by the Voron when we complete the write of the entire thing to stable storage. See the post above to a complete discussion on why it matters and how to do this quickly and with the least amount of pain.&lt;/p&gt; &lt;p&gt;Typically, the transaction journal is only used during recovery, so it is write only. We let the journal files to grow to about 64MB in size, then we create new ones. During database startup, we check what is the last journal file and journal file position that we have synced (more on that later), and we start reading from there. We read the transaction header and compare its hash to the hash of the compressed data. If they match (as well as a bunch of other checks we do), then we consider this to be a valid commit, and then we decompress the data into a temporary buffer and we have all the dirty pages that were written in that transaction.&lt;/p&gt; &lt;p&gt;We can then just copy them to the appropriate location in the data file. We continue doing so until we hit the end of the last file or we hit a transaction which is invalid or empty. At that point we stop, consider this the end of the valid committed transactions, and complete recovery.&lt;/p&gt; &lt;p&gt;Note that at this point, we have written a lot of stuff to the data file, but we have flushed it. The reason is that flushing is incredibly expensive, especially during data recovery where we might be re-playing a &lt;em&gt;lot &lt;/em&gt;of data. So we skip it.&amp;nbsp; Instead, we rely on the normal flushing process to do this for us. By default, this will happen within 1 minute of the database starting up, in the background, so it will reduce the interruption for regular operations. This gives us a very fast startup time. And our in memory state let us know where is the next place we need to flush from the log, so we don’t do the same work twice.&lt;/p&gt; &lt;p&gt;However, that does mean that if we fail midway through, there is absolutely no change in behavior. In recovery, we’ll write the same information to the same place, so replaying the journal file become idempotent operation that can fail and recover without a lot of complexity.&lt;/p&gt; &lt;p&gt;We do need to clear the journal files at some point, and this process happens after we synced the data file. At that point, we know that the data is safely stored in the data file, and we can update our persistent state on where we need to start applying recovery the next time. Once those two actions are done, we can delete the old (and now unused) journal files. Note that at each part of the operation, the failure mode is to simply retry the idempotent operation (copying the pages from the journal to the data file), and there is no need for complex recovery logic.&lt;/p&gt; &lt;p&gt;During normal operation, we’ll clear the journal files once it has been confirmed that all the data it has was successfully flushed to the disk &lt;em&gt;and&lt;/em&gt; that this action has been successfully recorded in stable storage. So in practice, database restarts after recovery are typically very fast, only needing to reply the last few transactions before we are ready for business again.&lt;/p&gt;</description><link>https://ayende.com/blog/175075/voron-internals-the-transaction-journal-recovery?Key=d500b56f-0ebb-44e9-ba1f-38ac4e81d6ff</link><guid>https://ayende.com/blog/175075/voron-internals-the-transaction-journal-recovery?Key=d500b56f-0ebb-44e9-ba1f-38ac4e81d6ff</guid><pubDate>Tue, 30 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Voron internals: Cleaning up scratch buffers</title><description>&lt;p&gt;&lt;img style="float: right; display: inline" src="https://s-media-cache-ak0.pinimg.com/564x/b2/be/6f/b2be6f7f8d9d4226c8482ba80da0fa12.jpg" align="right"&gt;&lt;/p&gt; &lt;p&gt;In &lt;a href="https://ayende.com/blog/175073/vorons-internals-mvcc-all-the-moving-parts?key=3860fe89441d4b6484c8991fcf6293fe"&gt;my previosus post&lt;/a&gt;, I talked about how Voron achieves MVCC. Instead of modifying data in place, we copy the page or pages we want to modify to a scratch buffer and modify that. When the write transaction completes, we are updating a Page Translation Table so any reference to the pages that were modified would go to the right place in the scratch file.&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Note, Voron uses mmap files as scratch buffers. I use the term scratch buffer / scratch file to refer to the same thing. &lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;That is all well and good, and if you are familiar with how virtual memory works, this is exactly the model. In effect, every transaction get a snapshot of the entire database as it was when it was opened. Read transactions don’t modify the data, and are ensured to have a stable snapshot of the database. The write transaction can modify the database freely, without worrying about locking or stepping over other transactions.&lt;/p&gt; &lt;p&gt;This is all pretty simple, and the sole cost that we have when committing the transaction is flushing all the dirty pages to disk, and then making an atomic pointer swap to update the Page Translation Table.&lt;/p&gt; &lt;p&gt;However, that is only part of the job, if all the data modifications happens on the scratch buffer, what is going on with the scratch files? &lt;/p&gt; &lt;p&gt;Voron has a background process that monitor the database activity, and based on certain policy (size, time, load factor, etc) it will routinely write the data from the scratch files to the data file. This is a bit of an involved process, because we can’t just do this blindly.&lt;/p&gt; &lt;p&gt;Instead, we start by seeing what is the oldest active transaction that is currently operating. We need to find that out to make sure that we aren’t writing any page that this transaction might visit (thus violating the snapshot isolation of the transaction). Once we have the oldest transaction, we gather all the pages from the Page Translation Table that came from older transactions and write them to the data file. There are a couple of tricks that we use here. It is very frequent for the same page to be modified multiple times (maybe we updated the record several times in different transactions), so we’ll have multiple copies of it. But we don’t actually need to copy all of them, we just need to copy the latest version (up to the oldest active transaction).&lt;/p&gt; &lt;p&gt;The process of copying all the data from the scratch file to the data file can happen concurrently with both read and write transactions. After the flush, we need to update the PTT again (so we open a very short write transactions to do that), and we are done. All the pages that we have copied from the scratch buffer are marked as free and are available for future transactions to use.&amp;nbsp; &lt;/p&gt; &lt;p&gt;Note, however, that we haven’t called fsync on the data file yet. So even though we wrote to the data file, we made a buffered write, which is awesome for performance, but not so much for safety. This is done intentionally, for performance reasons. In my next post, I’ll talk about recovery and safety at length, so I’ll just mention that we fsync the data file once a minute or one once every 2GB or so. The idea is that we give the OS the time to do the actual flush on the background, before we just in and demand that this will happen.&lt;/p&gt; &lt;p&gt;Another problem that we have with the scratch buffer is that, like any memory allocation routine, it has issues. In particular, it has to deal with fragmentation. We use power of two allocator to reduce fragmentation as much as possible, but certain workloads can fragment the memory in such a way that it is hard / impossible to deal with it. In order to deal with that issue, we keep track on not just the free sections in the scratch buffer, but also on the total amount of used memory. If a request cannot be satisfied by the scratch buffer because of fragmentation, but there is enough free space available, we’ll create a new scratch file and use that as our new scratch. The old one will eventually be freed when all read transactions are over and all the data has been flushed away.&lt;/p&gt; &lt;p&gt;Scratch files are marked as temporary and delete of close, so we don’t actually incur a high I/O cost when we create new ones, and it typically only when we have very high workload of both reads and writes that we see the need to create new scratch files.This tend to be drastically cheaper than trying to do compaction, and it actually work in all cases, while compaction can fail in many cases.&lt;/p&gt; &lt;p&gt;You might have noticed an issue with the whole system. We can only move pages from the scratch file to the data file if it was modified by a transaction that is older than the oldest current transaction. That means that a long running read transaction can stall the entire process. This typically is only a problem when we are seeing very high write usage as well as very long read transactions, which pushes the envelope on the size of the scratch buffer but at the same time doesn’t allow to clean it.&lt;/p&gt; &lt;p&gt;Indeed, using Voron, you are typically aware on the need to close transactions in a reasonable timeframe. Within RavenDB, there are very few places where a transaction can span a long time (streaming is pretty much the only case in which we’ll allow it, and it is documented that if you have a very long streaming request, that push memory usage on the server up because we can’t clean the transaction). In practice, even transactions that takes multiple minutes are fine under moderate write load, because there is enough capacity to handle it. &lt;/p&gt;</description><link>https://ayende.com/blog/175074/voron-internals-cleaning-up-scratch-buffers?Key=e246bcc0-c642-4b6c-9324-28d5a44a9a17</link><guid>https://ayende.com/blog/175074/voron-internals-cleaning-up-scratch-buffers?Key=e246bcc0-c642-4b6c-9324-28d5a44a9a17</guid><pubDate>Mon, 29 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Voron Internals: MVCC - All the moving parts</title><description>&lt;p&gt;&lt;img style="float: right; display: inline;" src="http://2.bp.blogspot.com/-sj4KZpmX2y4/UqRnuMxnquI/AAAAAAAAJLY/GGy7l2H6iOc/s1600/Steam_engine.png" alt="" align="right" /&gt;&lt;/p&gt;
&lt;p&gt;I talked about the different aspects of building a database engine in detail in the past month or so. But I tried to talk about each topic independently, so it will make sense. The problem is that in the real world, there are actually quite a lot of related stuff that impact on one another. This series of posts is meant to tie everything together, so you&amp;rsquo;ll have a better understanding how the design decisions in one place being affected by the requirement in somewhere that seems utterly unrelated.&lt;/p&gt;
&lt;p&gt;Before we can talk about the implementation details, let us see what we are trying to achieve. Voron is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High performance.&lt;/li&gt;
&lt;li&gt;Single write, multiple readers (MVCC)&lt;/li&gt;
&lt;li&gt;Fully ACID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, I&amp;rsquo;m not going to talk about the data model, or how we sort it, or anything like that. No, we are at a much lower level than that. We are at how we access the raw data pages and manage them.&lt;/p&gt;
&lt;p&gt;There are actually multiple players involved here. We have the journal for durability of writes, we have the data file to store the data, the scratch file to implement Multi Versioning Concurrency Control and the Page Translation Tables to provide snapshot isolation for concurrent transactions.&lt;/p&gt;
&lt;p&gt;The&amp;nbsp; design of Voron is immensely simplified by the fact that we choose to go with a single writer model. We share this design decision with other databases engines such as LMDB, LevelDB, RocksDB, etc. Concurrent write transactions are much more complex and require a lot more effort, and you still have the serialization at the journal level, although I explored multiple ways around it. With Voron, we decided to go with a single write transaction for the simplicity, and then implemented &lt;a href="https://ayende.com/blog/174945/the-guts-n-glory-of-database-internals-merging-transactions?key=81013acb67454013a5770884cba4159e"&gt;transaction merging&lt;/a&gt; on top of that, which give us a tremendous performance boost in high load scenarios.&lt;/p&gt;
&lt;p&gt;But let us talk about MVCC. The idea is that we have concurrent versions of the data, so each transaction has a snapshot of the entire database and can operate on that without fear of write transactions modifying data while it is running. Let us explore how this works when the database starts.&lt;/p&gt;
&lt;p&gt;The key to that is the notion of the page translation table, from now on, known as the PTT. When the database starts, we have an empty PTT, and the data file itself. We open a read transaction, which has the following data:&lt;/p&gt;
&lt;p&gt;ReadTx-1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PTT: [ /* no entries */&lt;/li&gt;
&lt;li&gt;Data file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whenever the read transaction need to read a page, it consults the PTT, find that there is nothing there, and read the page from the data file. We keep the read transaction open, and open a new write transaction. It also gets a PTT and the data file, but it also needs to keep track of a few other things:&lt;/p&gt;
&lt;p&gt;WriteTx-2:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PTT: [/* no entries */]&lt;/li&gt;
&lt;li&gt;Data file&lt;/li&gt;
&lt;li&gt;Dirty pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, we want to make a change to the database, which happens to fall on Page #3. Here we have problem, we can&amp;rsquo;t modify the data file directly, ReadTx-1 is still running, and it might want to read the data in Page #3 at some point. Instead of modifying the data directly, we copy the page into the scratch file.&lt;/p&gt;
&lt;p&gt;The scratch file is just a temporary file that we use to store data copies. After we copy the data, we update the PTT. Now when we search for Page #3, we&amp;rsquo;ll find the location of the page in the scratch file. As far as the write transaction is concerned, this doesn&amp;rsquo;t matter. A page is a page is a page, and it doesn&amp;rsquo;t matter where it is at.&lt;/p&gt;
&lt;p&gt;Committing the transaction means taking all of the dirty pages in the write transaction and writing them to the log. After which we atomically set the PTT for the write transaction as the global PTT. Now, all future read transactions will have the new PTT and when they will ask for Page #3, they will get the page from the scratch file.&lt;/p&gt;
&lt;p&gt;A new write transaction that needs to (again) modify Page #3, will create another copy of the Page inside the scratch file.This ends up looking like this:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/The-Guts-n-Glory-of_139E/image_2.png"&gt;&lt;img style="background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border: 0px;" title="image" src="https://ayende.com/blog/Images/Open-Live-Writer/The-Guts-n-Glory-of_139E/image_thumb.png" alt="image" width="581" height="278" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We have three copies of Page #3. One for the original read transaction (in the data file), one for the current read transactions (yellow in the scratch file) and the current modified page (orange in the scratch file) that we are writing to.&lt;/p&gt;
&lt;p&gt;When the write transaction completes, we again flush the dirty pages to the journal and then publish our PTT so all future transactions can see the changes.&lt;/p&gt;
&lt;p&gt;Of course, that is just one side of it, in my next post, I&amp;rsquo;ll discuss how we clear the scratch file and move data back to the data file.&lt;/p&gt;</description><link>https://ayende.com/blog/175073/voron-internals-mvcc-all-the-moving-parts?Key=3860fe89-441d-4b64-84c8-991fcf6293fe</link><guid>https://ayende.com/blog/175073/voron-internals-mvcc-all-the-moving-parts?Key=3860fe89-441d-4b64-84c8-991fcf6293fe</guid><pubDate>Fri, 26 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: Graph querying over large datasets</title><description>&lt;p&gt;I mentioned that &lt;a href="https://ayende.com/blog/175048/database-building-101-stable-node-ids?key=899e7abac0604fc2ae96b1ab06264800"&gt;maintaining physical ids is important for performance reasons&lt;/a&gt; in my previous post, but I skipped on exactly why. The short answer is that if I have a physical ids, it is much easier to implement locality and much easier to implement parallel locality.&lt;/p&gt;
&lt;p&gt;Let us imagine a database whose size is about 100GB, running on a machine that has 6 GB of RAM. You need to do run some sort of computation that traverse the graph, but doing so naively will likely cause us to trash quite a lot, as we page memory in and out of the disk, only to jump far away in the graph, paging even more, and effectively killing all your performance.&lt;/p&gt;
&lt;p&gt;Instead, we can do something like this, let us imagine that you have a machine with 4 cores on it, and the previous mention setup. And then you start 4 threads (each marked with a different color on the image, and start processing nodes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-Graph-querying-ove_1212D/image_8.png"&gt;&lt;img style="background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border: 0px;" title="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-Graph-querying-ove_1212D/image_thumb_3.png" alt="image" width="1053" height="576" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;However, there is a trick here, each thread has a queue, and only ids that fall without the area of responsibility of the thread will arrive there. But we aren&amp;rsquo;t done, &lt;em&gt;inside &lt;/em&gt;a thread we define additional regions, and route requests to process each region into each own queue.&lt;/p&gt;
&lt;p&gt;Finally, within each thread, we process one region at a time. So the idea is that while we are running over a region, we may produce work that will need to run on other regions (or even other threads), but we don&amp;rsquo;t care, we queue that work and continue emptying the work that exists on our own region. Only once once we have completed all work in a particular region will we move to the next one. The whole task complete when, in all threads, there are no more regions with work to be done.&lt;/p&gt;
&lt;p&gt;Note that the idea here is that each thread is working on one region at a time, and that region maps to a section of the database file that was memory mapped. So we keep that are of the page cache alive and well.&lt;/p&gt;
&lt;p&gt;When we move between regions, we can hint to the memory manager that we are going to need the next region, etc. We can&amp;rsquo;t escape the need to process the same region multiple times, because processing in one region may lead us to processing in another, and then back, but assuming we run the regions using least recently accessed, we can take advantage on the stuff remaining in the page cache (hopefully) from the previous run and using &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Which is why the physical location on disk is important.&lt;/p&gt;
&lt;p&gt;Note that the actual query that we run is less important. Typical graph queries are fall into one of two categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some sort of Breadth First Search or Depth First Search and walking through the graph.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Finding a sub-graph in the larger graph that matches this criteria.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases, we can process such queries using the aforementioned process, and the reduction in random work that the database has to do is&amp;nbsp;&lt;em&gt;big&lt;/em&gt;.&lt;/p&gt;</description><link>https://ayende.com/blog/175049/database-building-101-graph-querying-over-large-datasets?Key=20d0ece3-d177-45d0-be17-a7b75a6ef286</link><guid>https://ayende.com/blog/175049/database-building-101-graph-querying-over-large-datasets?Key=20d0ece3-d177-45d0-be17-a7b75a6ef286</guid><pubDate>Thu, 25 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: Stable node ids</title><description>&lt;p&gt;A few posts ago, I talked about the problem of having &lt;a href="https://ayende.com/blog/175045/database-building-101-graphs-arent-toys?key=070b399c111a49afab9e525a3e9af5d4"&gt;unstable ids&lt;/a&gt;, in particular, ids that can be reused. That leads to quite a lot of complexity, as anyone who ever had to deal with Lucene documents ids knows.&lt;/p&gt; &lt;p&gt;So we are willing to pay something toward stable ids, the questions is what?&lt;/p&gt; &lt;p&gt;One way of doing that is to just store the physical id (unstable) and a virtual id (stable) in a B+Tree (actually, a pair of them, since you’ll need to refer to them back and forth). That means that for the most part, internally to the engine, we’ll use the physical id (with its nice property of having O(1) access time), but externally we’ll expose the stable virtual id (probably sequential numbering, since that is easiest).&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Note that I still want to use the physical ids, I’ll discuss exactly why that is important in my next post, for now, let us just say that it is an important component of ensuring high performance for large datasets.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;The problem with using B+Tree is that the cost of finding the virtual &amp;lt;—&amp;gt; physical id mapping is O(logN), which for 10 million nodes and 100 million edges is 23 &amp;amp; 24 respectively. Except that this isn’t the real cost function for B+Tree.&lt;/p&gt; &lt;p&gt;Assuming that we have 255 items per page, we actually would need to do 4 page lookups, and a total of 54 comparisons to find the right value. For the edges, we would need 5 page look ups and over 60 comparisons.&amp;nbsp; Note that this isn’t an issue on its own, but it is an issue when we are talking about having this kind of cost in the hot path of the application. And this is very likely &lt;em&gt;going&lt;/em&gt; to be in the hot path. &lt;/p&gt; &lt;p&gt;Oh, there are ways around it, we can only translate back and forth at the edges of the database, so internally we’ll always use the physical address, and only translate it out when we are done. But that is hard to actually do properly, since you need the virtual address for a whole lot of stuff all over the place.&lt;/p&gt; &lt;p&gt;We can steal the idea of page translation tables from the processor. Something like this:&lt;/p&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-Stable-node-ids_11C22/image_2.png"&gt;&lt;img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-Stable-node-ids_11C22/image_thumb.png" width="854" height="568"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Effectively, we’ll lazy allocate segments of pages and pull them together into a hierarchy. So finding out the physical address of id 84 would involve looking at the root, finding the next page down with mod operation, and so forth until we find the right value and check there. This has the advantage of being simple, O(1) and obvious. It is also pretty good in terms of space saving, since the virtual id can be “stored” without taking any space (it is the position of the physical id in the “array” we created.&lt;/p&gt; &lt;p&gt;This has one drawback, there is no way to recover space. Because the indexer into this data structure is meaningful, we can’t just compact things. Once space is allocated, that is it.&amp;nbsp; Now, to be fair, the cost in size here for all 100 million edges is about 0.75 GB, so not meaningful in the long run, but if we have a busy database that always write and delete, we have no way to recover the space.&lt;/p&gt; &lt;p&gt;The “proper” answer, by the way, is to implement an external hash table. That has the property of O(1), can grow and shrink as the amount of data changes. I’m not presenting it here mostly because it is something that we haven’t yet had the need to implement in Voron, so it isn’t something that I can just show and move on. Beside, it is fun to explore all the wrong ways of doing something.&lt;/p&gt;</description><link>https://ayende.com/blog/175048/database-building-101-stable-node-ids?Key=899e7aba-c060-4fc2-ae96-b1ab06264800</link><guid>https://ayende.com/blog/175048/database-building-101-stable-node-ids?Key=899e7aba-c060-4fc2-ae96-b1ab06264800</guid><pubDate>Wed, 24 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: High level graph operations</title><description>&lt;p&gt;I talked about high level and low level data operations. So far, all we have &lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-High-level-graph-o_9526/image_2.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-High-level-graph-o_9526/image_thumb.png" width="265" align="right" height="281"&gt;&lt;/a&gt;seen are very low level operations (get node, get edges for, etc).&lt;/p&gt; &lt;p&gt;Let us see how we’ll deal with a bigger challenge. In this case, we want to implement a classic graph operation, doing a depth first search, filtering by both nodes and edges. &lt;/p&gt; &lt;p&gt;Here is how we can implement this:&lt;/p&gt; &lt;blockquote&gt;&lt;script src="https://gist.github.com/ayende/817c2de2c4aec389308519925b3da67e.js"&gt;&lt;/script&gt;&lt;/blockquote&gt; &lt;p&gt;In the real world, we’ll need quite a bit more. On each node (and edge) we’ll need to decide if to return it from the query, or just traverse through it, etc. And that is just to start with.&lt;/p&gt; &lt;p&gt;But I think this demonstrate the point of how to layer behavior on top of the lower level database operations.&lt;/p&gt; &lt;p&gt;There is one thing that we need to talk about still, this code will actually use a lot of individual transactions, one for each independent operation. That is quite expensive, we can open a single transaction and pass it to the functions we call, so there is just a single cost for the entire duration of the operation.&lt;/p&gt; &lt;p&gt;Other things we can do is to explicitly designate specific scenarios as important and change the design so we can answer them very quickly (as in the O(1) cost for accessing nodes/edge data).&lt;/p&gt;</description><link>https://ayende.com/blog/175046/database-building-101-high-level-graph-operations?Key=476896f3-087c-4cfc-abd9-e61ceb95e1ee</link><guid>https://ayende.com/blog/175046/database-building-101-high-level-graph-operations?Key=476896f3-087c-4cfc-abd9-e61ceb95e1ee</guid><pubDate>Mon, 22 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: Graphs aren’t toys</title><description>&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-Graphs-arent-toys_14C08/image_5.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-Graphs-arent-toys_14C08/image_thumb_1.png" width="294" align="right" height="215"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;I&amp;nbsp; keep calling this a toy database, and it is true for more reasons than the code existing mostly as unconnected snippets. When defining &lt;a href="https://ayende.com/blog/175042/database-building-101-the-storage-layer?key=e5430dbf75df44d091693d1fa1354620"&gt;the storage layer&lt;/a&gt;, I breezed through quite a lot of stuff because they didn’t really matter for the point I was trying to make.&lt;/p&gt; &lt;p&gt;We’ll start with talking about node IDs. When we save a node to the database, we get an int64 ID back. What is that? We know that it gives us an O(1) access time to the node (or the edge data), but that’s about it. Typically, we don’t expose the internal table ID in this manner. Because the ID we get back from the Insert corresponds to the location on the disk where that data exists. So the node ID is something that is arbitrary and doesn’t correlate to anything. It isn’t a sequential number, or something that the user defines, or anything like that.&lt;/p&gt; &lt;p&gt;That can lead to issues. In particular, if you want to look up a node by some property, you need to have a way to do so that will retrieve its ID, and only then you can do graph operations from there. The common solution is to use a Lucene index for searching by the properties and finding the root node(s) and proceed from there.&lt;/p&gt; &lt;p&gt;But what about deletes? Deleting a node is possible, and when you do that, the space that was reserved for that node will be freed, and can be reused, so you’ll have a different node with the same ID. This leads to some awkwardness (you can see that with the Lucene document IDs, which have the same problem, although for very different reasons).&lt;/p&gt; &lt;p&gt;Updates also pose a problem, because if you need to extend the size of the node, it might be relocated, which changes the ID. Deleting is a challenge, because you need to remove the deleted node ID from all the edges that reference it, instead of cleaning it up on the fly.&lt;/p&gt; &lt;p&gt;This leads us to either abstract the physical ID with a virtual one (and pay the O(logN) cost for resolving it) or find a way to deal with the above inside your API.&lt;/p&gt;</description><link>https://ayende.com/blog/175045/database-building-101-graphs-arent-toys?Key=070b399c-111a-49af-ab9e-525a3e9af5d4</link><guid>https://ayende.com/blog/175045/database-building-101-graphs-arent-toys?Key=070b399c-111a-49af-ab9e-525a3e9af5d4</guid><pubDate>Fri, 19 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: The cost of graphing</title><description>&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-The-cost-of-graphi_14BB2/image_2.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Database-Building-101-The-cost-of-graphi_14BB2/image_thumb.png" width="184" align="right" height="147"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;So far we looked into how we can store the nodes and the edges, and we explored some interesting data structures inside Voron. Now, let’s see how we can traverse the graph.&lt;/p&gt; &lt;blockquote&gt;&lt;script src="https://gist.github.com/ayende/24ce83f2968af01ed728e104ca583db7.js"&gt;&lt;/script&gt;&lt;/blockquote&gt; &lt;p&gt;So getting the node is pretty easy, and remember that to access the table by ID gives us an O(1) cost.&lt;/p&gt; &lt;p&gt;Now, what about finding all the edges from a node?&lt;/p&gt; &lt;blockquote&gt;&lt;script src="https://gist.github.com/ayende/c7bf4f947a0c3d5efac7ac6e55e37370.js"&gt;&lt;/script&gt;&lt;/blockquote&gt; &lt;p&gt;This is a lot heftier, but let’s try to break it into individual pieces. First, we find the tree holding all the edges of that particular type, then we access (by the ID of the source node) the fixed size tree that holds all the connected nodes and the edge data ID.&lt;/p&gt; &lt;p&gt;Finally, we access the edges data (if it exists) to get the data about the edge itself, after which, we return it all to the caller.&lt;/p&gt; &lt;p&gt;Unlike the previous method, here we can’t claim to have O(1) costs. Instead, our costs are composed of:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;O(logN) – where N is the number of edges types (typically, very few), to search for the right edges tree.  &lt;li&gt;O(logN) – where N is the number of source nodes (typically, very large, but the fixed size trees are very good at being fast, and they don’t have to do much).  &lt;li&gt;O(N) – where N is the number of connection between the source node and the destination node. &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;I’m excluding the cost of loading the edge data, since this is an O(1) operation and is directly relevant to the iteration over all nodes connected to the source node.&lt;/p&gt; &lt;p&gt;Ideally, we can find a way to turn the 2nd operation into an O(1) cost, but that should be more than good enough for the moment.&lt;/p&gt; &lt;p&gt;Now, this just gives us traversal of the nodes, but going from here to Breadth First Search / Depth First Search and doing them &lt;em&gt;well&lt;/em&gt; is fairly trivial, although there are a few things that we'll need to talk about, but that would serve as a separate post.&lt;/p&gt;</description><link>https://ayende.com/blog/175043/database-building-101-the-cost-of-graphing?Key=38fa0646-25cf-48a1-a31f-4d3f1a037f23</link><guid>https://ayende.com/blog/175043/database-building-101-the-cost-of-graphing?Key=38fa0646-25cf-48a1-a31f-4d3f1a037f23</guid><pubDate>Wed, 17 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: The storage layer</title><description>&lt;p&gt;&lt;img style="float: right; display: inline;" src="https://www.kidscodecs.com/tyto/wp-content/uploads/2013/11/puzzles-7-bridges-map-euler.jpg" alt="" width="240" height="114" align="right" /&gt;&lt;/p&gt;
&lt;p&gt;We are going to be using Voron to actually handle the storage of the data. Voron is a low level storage engine, which provide, among other things, fully ACID, MVCC and high performance.&lt;/p&gt;
&lt;p&gt;For today, what'll look at are the following operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a node.&lt;/li&gt;
&lt;li&gt;Add an edge between two nodes.&lt;/li&gt;
&lt;li&gt;Traverse from a node to all its edges (cheaply)&lt;/li&gt;
&lt;li&gt;Traverse from an edge to the other node (cheaply).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the interface code that we will need to build:&lt;/p&gt;
&lt;blockquote&gt;
&lt;script src="https://gist.github.com/ayende/a4bbeb7c01879d2dbbe15f2fe46d34ae.js"&gt;&lt;/script&gt;
&lt;/blockquote&gt;
&lt;p&gt;I tried to make it as simple as it possible could be. I&amp;rsquo;m using NameValueCollection because it is a trivial property bag to serialize / deserialize without bringing any additional dependencies.&lt;/p&gt;
&lt;p&gt;Let us focus on the initialization of this class. We need to create a StorageEnvironment in Voron, and setup some structures.&lt;/p&gt;
&lt;blockquote&gt;
&lt;script src="https://gist.github.com/ayende/9772ad7507fcf0bde65e777912485dbd.js"&gt;&lt;/script&gt;
&lt;/blockquote&gt;
&lt;p&gt;Voron has several different storage options for data, and in this case, we are using a table. A table (which is very much unlike a RDMBS table) it a way to store records that has some indexes, but in this case, we are doing something strange. We are defining a table that has no indexes (this is so strange that we need to explicitly allow it). This is useful because tables manages indexes for us automatically, but in this case we will use them strictly for storage. We&amp;rsquo;ll see why in just a bit.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote&gt;
&lt;script src="https://gist.github.com/ayende/8068313282fa1c21bdde5574f6a7c9d7.js"&gt;&lt;/script&gt;
&lt;/blockquote&gt;
&lt;p&gt;The code here is a bit strange. Tables are actually meant to hold multiple values, and define indexes on this. So using them to store a single value is something that you wouldn&amp;rsquo;t normally do. Except that the id that we get back from the table has a very interesting property. It has O(1) cost of access. So given a node id, I can access it directly, regardless of how big my database is. That is a property that I want, because effectively random access of nodes is something that happens quite a lot and is highly desirable.&lt;/p&gt;
&lt;p&gt;Now, let us see how we can connect two edges, shall we. This code ignores all error handling, missing nodes, etc. It is meant to explain the core stuff, this is a toy database, after all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;script src="https://gist.github.com/ayende/02cb2ec5691720a8180f2ce89d07ce8d.js"&gt;&lt;/script&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here we continue doing strange stuff. We already know that we use the empty schema table to have an O(1) access to data. And we store the edge&amp;rsquo;s data there. But then we run into some new stuff. We create a B+Tree called &amp;ldquo;Edges_&amp;rdquo;+type, which hold all of the edges of a particular type. But the &lt;em&gt;content&lt;/em&gt; of this B+Tree is not simple. Instead, it is using fixed size trees. Those are also B+Trees, but they have well known size both for keys (which must be long) and the value (which must be small &amp;lt; 256 bytes). Because they are very compact, we can pack quite a lot of data into them, and work with them efficiently.&lt;/p&gt;
&lt;p&gt;The end result is that we are now storing the node data and access it at O(1) cost. We also store a B+Tree full of fixed size tree (whose name is the source node id) and whose keys are the destination nodes, and the values are the edge data.&lt;/p&gt;
&lt;p&gt;Confusing yet? Not much different than Dictionary&amp;lt;SourceNodeId, Dictionary&amp;lt;DestinationNodeId, EdgeDataId&amp;gt;&amp;gt;. That is quite enough for today, tomorrow I&amp;rsquo;ll show how we can traverse the data, and what kind of costs are associated with it.&lt;/p&gt;</description><link>https://ayende.com/blog/175042/database-building-101-the-storage-layer?Key=e5430dbf-75df-44d0-9169-3d1fa1354620</link><guid>https://ayende.com/blog/175042/database-building-101-the-storage-layer?Key=e5430dbf-75df-44d0-9169-3d1fa1354620</guid><pubDate>Tue, 16 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Database Building 101: Let us graph this for real</title><description>&lt;p&gt;&lt;img style="float: right; display: inline" alt="" src="https://cdn4.dogonews.com/system/ckeditor_assets/pictures/50688b791860e0275b000f45/content_bbeeTSP2.jpg" align="right"&gt;In the &lt;a href="https://ayende.com/blog/posts/series/174337/the-guts-n-glory-of-database-internals"&gt;Guts n’ Glory of Database Internals&lt;/a&gt; posts series (which I’ll probably continue if people suggest new topics), I talked about the very low level things that are involved in actually building a database. From how to ensure consistency to the network protocols. But those are very low level concerns. &lt;em&gt;Important&lt;/em&gt; ones, but very low level. In this series, I want to start going up a bit in the stack and actually implement a toy database on top of real production system, to show you what the database engine actually does.&lt;/p&gt; &lt;p&gt;In practice, we divide the layers of a database engine this way:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Low level storage (how we save the bits to disk), journaling, ACID.  &lt;li&gt;High level storage (what kind of storage options do we have, B+Tree, nested trees, etc).  &lt;li&gt;Low level data operations (working on a single item at time).  &lt;li&gt;High level data operations (large scale operations, typically).  &lt;li&gt;Additional features (subscribing to changes, for example). &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;In order to do something interesting, we are going to be writing a toy graph database. I’m going to focus on levels 3 &amp;amp; 4 here, the kind of data operations that we need to provide the database we want, and we are going to build over pre-existing storage solution that handles 1 &amp;amp; 2.&lt;/p&gt; &lt;p&gt;Selecting the storage engine – sometimes it make sense to go elsewhere for the storage engine. Typical examples includes using LMDB or LevelDB as embedded databases that handles the storage, and you build the data operations on top of that. This &lt;em&gt;works&lt;/em&gt;, but it is limiting. You can’t do certain things, and sometimes you really want to. For example, LMDB supports the notion of multiple trees (and even recursive trees), while LevelDB has a flat key space. That has a big impact on how you design and build the database engine.&lt;/p&gt; &lt;p&gt;At any rate, I don’t think it will surprise anyone that I’m using Voron as the storage engine. It was developed to be a very flexible storage engine, and it works very well for the purpose.&lt;/p&gt; &lt;p&gt;We’ll get to the actual code in tomorrow’s post, but let’s lay out what we want to end up with.&lt;/p&gt; &lt;ul&gt; &lt;li&gt;The ability to store nodes (for simplicity, a node is just an arbitrary property bag).  &lt;li&gt;The ability to connect nodes using edges.  &lt;ul&gt; &lt;li&gt;Edges belong to types, so KNOWS and WORKED_AT are two different connection types.  &lt;li&gt;An edge can be bare (no properties) or have data (again, for simplicity, just arbitrary property bag) &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;The purpose of the toy database we build is to allow the following low level operations:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Add a node.  &lt;li&gt;Add an edge between two nodes.  &lt;li&gt;Traverse from a node to all its edges (cheaply)  &lt;li&gt;Traverse from an edge to the other node (cheaply). &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;That is it, should be simple enough, right?&lt;/p&gt;</description><link>https://ayende.com/blog/175041/database-building-101-let-us-graph-this-for-real?Key=e0d7e3d2-cd98-4c23-97d6-ae8f1f1ba337</link><guid>https://ayende.com/blog/175041/database-building-101-let-us-graph-this-for-real?Key=e0d7e3d2-cd98-4c23-97d6-ae8f1f1ba337</guid><pubDate>Mon, 15 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Digging into the CoreCLR: Exceptional costs, Part I</title><description>&lt;p&gt;Note, this post was written by &lt;a href="https://twitter.com/federicolois"&gt;Federico&lt;/a&gt;.  &lt;p&gt;One guideline which is commonly known is: "Do not use exceptions for flow control." You can read more about it in many places, but this is good compendium of &lt;a href="http://c2.com/cgi/wiki?DontUseExceptionsForFlowControl"&gt;the most common arguments&lt;/a&gt;. If you are not acquainted with the reasons, give them a read first; I’ll wait.&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt;  &lt;p&gt;&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;&lt;img style="float: right; display: inline" src="http://4.bp.blogspot.com/_YS7MgyvJ0A8/Rq4FCK57mZI/AAAAAAAAAQs/-Kr3Md3WG9s/s200/falling_knife-708336.gif" align="right"&gt;Many of the reasons focus on the readability of the code, but remember, my work (usually) revolves around writing pretty disgusting albeit efficient code. So even though I care about readability it is mostly achieved through very lengthy comments on the code on why you shouldn't touch something if you cannot prove something will be faster. &lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;Digression aside the question is still open. What is the impact of using exceptions for control flow (or having to deal with someone else throwing exceptions) in your performance sensitive code? Let's examine that in detail.&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt;  &lt;p&gt;&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;For that we will use a very simple code to understand what can happen.&amp;nbsp; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_2.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_thumb.png" width="522" height="421"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;This is a code that is simple enough so that the assembler won’t get too convoluted, but at the same time sport at least some logic we can use as markers.&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt;  &lt;p&gt;&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;Let's first inspect the method &lt;b&gt;&lt;i&gt;CanThrow&lt;/i&gt;&lt;/b&gt;, in there what we can see is how the throwing of exceptions happen:&lt;/p&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_4.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_thumb_1.png" width="636" height="163"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;As you can see there is a lot of things to be done just to throw the exception. There in the last call we will use jump to the proper place in the stack and continue in the catch statement that we hit.&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt;  &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_8.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_thumb_3.png" width="905" height="683"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;So here is the code of our simple method. At the assembler level, our try statement has a very important implication. Each try-catch forces the method to deal with a few control flow issues. First it has to store the exception handler in case anything inside would throw, then it has to do the actual work. If there is no exception (the happy path) we move forward and end. But what happen if we have an exception? We first need to remove the handler (we don't want to recheck this handler if we end up throwing inside the catch, right?) Then execute the catch and be done. &lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;But now let’s contrast that to the generated code if no try-catch statement happens. The avid reader will realize that the happy path will never be executed because we are throwing, but don’t worry, the code is the same if there is no inlining happening. &lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;&lt;u&gt;&lt;/u&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_10.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/e7fc0c071fec_7100/image_thumb_4.png" width="571" height="184"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;We will talk about why the code ends up like this in a follow up post, but suffice to say that all this trouble cannot beat a check for a Boolean if you needed to fail (and could do something about it).&amp;nbsp; &lt;p&gt;It is also important to remember that this kind of work is only relevant if you are in the hot path. If you are not calling a function at least a few tens of thousands a second, don’t even bother, your performance costs are elsewhere. This is micro optimization land.&lt;/p&gt;</description><link>https://ayende.com/blog/175009/digging-into-the-coreclr-exceptional-costs-part-i?Key=cc3c53d3-2a1c-407c-a884-f5a3f5e32bec</link><guid>https://ayende.com/blog/175009/digging-into-the-coreclr-exceptional-costs-part-i?Key=cc3c53d3-2a1c-407c-a884-f5a3f5e32bec</guid><pubDate>Thu, 11 Aug 2016 09:00:00 GMT</pubDate></item><item><title>Challenge: The race condition in the TCP stack, answer</title><description>&lt;p&gt;In my &lt;a href="https://ayende.com/blog/174881/challenge-the-race-condition-in-the-tcp-stack?key=0176b3627d984a1488b7228541d90f11"&gt;previous post&lt;/a&gt;, I discussed a problem in missing data over TCP connection that happened in a racy manner, only every few hundred runs. As it turns out, there is a simple way to make the code run into the problem every single time.&lt;/p&gt; &lt;p&gt;The &lt;a href="https://gist.github.com/ayende/46cb25889fca9dcb95aa242ecaaa38df"&gt;full code for the repro can be found here&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Change &lt;a href="https://gist.github.com/ayende/46cb25889fca9dcb95aa242ecaaa38df#file-tcp-race-cs-L33"&gt;these lines&lt;/a&gt;:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Challenge-The-race-condition-in-the-TCP-_A511/image_2.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/Challenge-The-race-condition-in-the-TCP-_A511/image_thumb.png" width="847" height="122"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;And voila, you will consistently run into the problem .&amp;nbsp; Wait, run that by me again, what is going on here?&lt;/p&gt; &lt;p&gt;As it turns out, the issue is in the server, more specifically, &lt;a href="https://gist.github.com/ayende/46cb25889fca9dcb95aa242ecaaa38df#file-tcp-race-cs-L59"&gt;here&lt;/a&gt; and &lt;a href="https://gist.github.com/ayende/46cb25889fca9dcb95aa242ecaaa38df#file-tcp-race-cs-L73"&gt;here&lt;/a&gt;. We use a StreamReader to read the first line from the client, do some processing, and then hand it to the ProcessConnection method, which also uses a StreamReader. More significantly, it uses a &lt;em&gt;different&lt;/em&gt; StreamReader.&lt;/p&gt; &lt;p&gt;Why is that significant? Well, &lt;a href="https://github.com/dotnet/corefx/blob/master/src/System.IO/src/System/IO/StreamReader.cs#L41"&gt;because of this&lt;/a&gt;, the StreamReader has buffers, by default, that are 1KB in size. So here is what happens in the case above: we send a single packet to the server, and when the first StreamReader reads from the stream, it fills the buffer with the two messages. But since there is a line break between them, when we call ReadLineAsync, we actually only get the first one.&lt;/p&gt; &lt;p&gt;Then, we when get to the ProcessConnection method, we have another StreamReader, which also reads from the stream, but the second message had already been read (and is waiting in the first StreamReader buffer), so we are waiting for more information from the client, which will never come.&lt;/p&gt; &lt;p&gt;So how come it sort of works if we do this in two separate calls? Well, it is all about the speed. In most cases, when we split it into two separate calls, the server socket has only the first message in there when the first StreamReader runs, so the second StreamReader is successful in reading the second line. But in some cases, the client manages being fast enough and sending both messages to the server before the server can read them, and voila, we have the same behavior, only much more unpredictable.&lt;/p&gt; &lt;p&gt;The key problem was that it wasn’t obvious we were reading too much from the stream, and until we figured that one out, we were looking in a completely wrong direction.&amp;nbsp; &lt;/p&gt;</description><link>https://ayende.com/blog/174882/challenge-the-race-condition-in-the-tcp-stack-answer?Key=80bca8c1-cc5e-4c57-af95-4bca0c9b5b81</link><guid>https://ayende.com/blog/174882/challenge-the-race-condition-in-the-tcp-stack-answer?Key=80bca8c1-cc5e-4c57-af95-4bca0c9b5b81</guid><pubDate>Tue, 26 Jul 2016 09:00:00 GMT</pubDate></item><item><title>Challenge: The race condition in the TCP stack</title><description>&lt;p&gt;&lt;img style="float: right; display: inline" src="https://s-media-cache-ak0.pinimg.com/236x/cf/f5/80/cff580ae4f82b8eb19beaf3b8122beb7.jpg" align="right"&gt;Occasionally, one of our tests hangs. Everything seems to be honky dory, but it just freezes and does not complete. This is a new piece of code, and thus is it suspicious unless proven otherwise, but an exhaustive review of it &lt;em&gt;looked &lt;/em&gt;fine. It took over two days of effort to narrow it down, but eventually we managed to point the finger directly at this line of code:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/The-race-condition-in-the-TCP-stack_A01E/image_2.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/The-race-condition-in-the-TCP-stack_A01E/image_thumb.png" width="652" height="35"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;In certain cases, this line would simply not read anything on the server. Even though the client most definitely sent the data. Now, given that TCP is being used, dropped packets might be expected. But we are actually testing on the loopback device, which I expect to be reliable.&lt;/p&gt; &lt;p&gt;We spent a &lt;em&gt;lot&lt;/em&gt; of time investigating this, ending up with a very high degree of certainty that the problem was in the TCP stack somewhere. Somehow, on the loopback device, we were losing packets. Not always, and not consistently, but we were absolutely losing packets, which led the server to wait indefinitely for the client to send the message it already did.&lt;/p&gt; &lt;p&gt;Now, I’m as arrogant as the next developer, but even I don’t think I found &lt;em&gt;that&lt;/em&gt; big a bug in TCP. I’m pretty sure that if it was this broken, I would have known about it. Beside, TCP is supposed to retransmit lost packets, so even if there were lost packets on the loopback device, we should have recovered from that.&lt;/p&gt; &lt;p&gt;Trying to figure out what was going on there sucked. It is hard to watch packets on the loopback device in WireShark, and tracing just told me that a message is sent from the client to the server, but the server never got it.&lt;/p&gt; &lt;p&gt;But we continued, and we ended up with a small reproduction of the issue. Here is the code, and my comments are below:&lt;/p&gt; &lt;blockquote&gt;&lt;script src="https://gist.github.com/ayende/46cb25889fca9dcb95aa242ecaaa38df.js"&gt;&lt;/script&gt;&lt;/blockquote&gt; &lt;p&gt;This code is pretty simple. It starts a TCP server, and listens to it, and then it reads and writes to the client. Nothing much here, I think you’ll agree.&lt;/p&gt; &lt;p&gt;If you &lt;em&gt;run&lt;/em&gt; it, however, it will mostly work, except that sometimes (anywhere between 10 runs and 500 runs on my machine), it will just hang. I’ll save you some time and let you know that there are no dropped packets, TCP is working properly in this case. But the code just doesn’t. What is frustrating is that it is &lt;em&gt;mostly&lt;/em&gt; working, it takes a lot of work to actually get it to fail.&lt;/p&gt; &lt;p&gt;Can you spot the bug? I’ll continue discussion of this in my next post.&lt;/p&gt;</description><link>https://ayende.com/blog/174881/challenge-the-race-condition-in-the-tcp-stack?Key=0176b362-7d98-4a14-88b7-228541d90f11</link><guid>https://ayende.com/blog/174881/challenge-the-race-condition-in-the-tcp-stack?Key=0176b362-7d98-4a14-88b7-228541d90f11</guid><pubDate>Mon, 25 Jul 2016 09:00:00 GMT</pubDate></item><item><title>How timers works in the CLR</title><description>&lt;p&gt;One of the coolest things about the CoreCLR being open sourced is that I can trawl through the source code and read random parts of the framework. One of the reasons to do this, is to be able to understand the implementation concerns, not just the design, which than allows us to produce a much better output.&lt;/p&gt; &lt;p&gt;In this case, I was investigating a hunch, and I found myself deep inside the code that runs the timers in .NET. The relevant code is &lt;a href="https://github.com/dotnet/coreclr/blob/d88b3c5fba00695b92adf0f3404c0f5b07acfe63/src/mscorlib/src/System/Threading/Timer.cs"&gt;here&lt;/a&gt;, and it is clearly commented as well as quite nice to read.&lt;/p&gt; &lt;p&gt;I’m just going to summarize a few interesting things I found in the code. &lt;/p&gt; &lt;p&gt;There is actually only one single real timer for the entire .NET process. I started out thinking this is handled via &lt;a href="https://msdn.microsoft.com/en-us/library/ms682485(v=vs.85).aspx"&gt;CreateTimerQueueTimer&lt;/a&gt; on Windows, but I couldn’t find a Linux implementation. Reading the code, the CLR actually implements this directly via &lt;a href="https://github.com/dotnet/coreclr/blob/726d1a3244b80bf963fd0d51e57d4bb90af1e426/src/vm/win32threadpool.cpp#L4997"&gt;this code&lt;/a&gt;. Simplified, it does the following:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/How-timers-works-in-the-CLR_11FC7/image_2.png"&gt;&lt;img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://ayende.com/blog/Images/Open-Live-Writer/How-timers-works-in-the-CLR_11FC7/image_thumb.png" width="540" height="242"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;This has some interesting implications. It means that timers are all going to be fired from the same thread, at the same time (not quite true, see below), and that there is likely going to be a problem with very long timers (a timer for three months from now will overflow int32, for example).&lt;/p&gt; &lt;p&gt;The list of timers is held in a linked list, and every time it is awakened, it runs through the list, finding the timer to trigger, and the next time to be triggered. The code in this code path is called with only a single timer, which is then used in the managed code for actually implementing the managed timers. It is important to note that actually running the timer callback is done by queuing that on the thread pool, not executing it on the timer thread.&lt;/p&gt; &lt;p&gt;On the &lt;a href="https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Threading/Timer.cs"&gt;managed side&lt;/a&gt;, there are some interesting comments explaining the expected usage and data structures used. There are two common cases, one is the use of timeout, in which case this is typically discarded before actual use, and the other is having the recurring timers, which tend to happen once in a long while. So the code favors adding / removing timers over actually finding which need to be executed.&lt;/p&gt; &lt;p&gt;Another thing to note is that this adding / removing / changing / iterating over timers is protected by a single lock. Every time the unmanaged timer wakes, it queue the callback on the thread pool, and then the &lt;a href="https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Threading/Timer.cs#L315"&gt;FireNextTimers&lt;/a&gt; is called, which takes a look, iterates over all the timers, and queues all those timers to be executed on the thread pool. &lt;/p&gt; &lt;p&gt;This behavior is interesting, because it has some impact on commonly used cases. But I’ll discuss that on my next post.&lt;/p&gt;</description><link>https://ayende.com/blog/174850/how-timers-works-in-the-clr?Key=1c566b4d-4157-4174-ba9b-5d4fd9e637ac</link><guid>https://ayende.com/blog/174850/how-timers-works-in-the-clr?Key=1c566b4d-4157-4174-ba9b-5d4fd9e637ac</guid><pubDate>Thu, 21 Jul 2016 09:00:00 GMT</pubDate></item><item><title>The Guts n’ Glory of Database Internals: What the disk can do for you</title><description>&lt;p&gt;I&amp;rsquo;m currently in the process of getting some benchmark numbers for a process we have, and I was watching some metrics along the way. I have mentioned that disk&amp;rsquo;s speed can be effected by quite a lot of things. So here are two metrics, taken about 1 minute apart in the same benchmark.&lt;/p&gt;
&lt;p&gt;This is using a &lt;a href="http://www.samsung.com/semiconductor/products/flash-storage/client-ssd/MZNLN512HCJH?ia=831"&gt;Samsung PM871 512GB SSD&lt;/a&gt; drive, and it is currently running on a laptop, so not the best drive in the world, but certainly a respectable one.&lt;/p&gt;
&lt;p&gt;Here is the steady state operation while we are doing a lot of write work. Note that the response time is very high, in computer terms, forever and a half:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/0564d02febc9_D090/image_2.png"&gt;&lt;img style="background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border: 0px;" title="image" src="https://ayende.com/blog/Images/Open-Live-Writer/0564d02febc9_D090/image_thumb.png" alt="image" width="262" height="119" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And here is the same operation, but now we need to do some cleanup and push more data to the disk, in which case, we get &lt;em&gt;great&lt;/em&gt; performance.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/0564d02febc9_D090/image_4.png"&gt;&lt;img style="background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border: 0px;" title="image" src="https://ayende.com/blog/Images/Open-Live-Writer/0564d02febc9_D090/image_thumb_1.png" alt="image" width="265" height="121" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But oh dear good, just look at the latency numbers that we are seeing here.&lt;/p&gt;
&lt;p&gt;Same machine, local hard disk (and SSD to boot), and we are seeing latency numbers that aren&amp;rsquo;t even funny.&lt;/p&gt;
&lt;p&gt;In this case, the reason for this is that we are flushing the data file along side the journal file. In order to allow to to proceed as fast as possible, we try to parallelize the work so even though the data file flush is currently holding most of the I/O, we are still able to proceed with minimal hiccups and stall as far as the client is concerned.&lt;/p&gt;
&lt;p&gt;But this can really bring home the fact that we are actually playing with a very limited pipe, and there little that we can do to control the usage of the pipe at certain points (a single fsync can flush a lot of unrelated stuff) and there is no way to throttle things and let the OS know (this particular flush operation should take more than 100MB/s, I&amp;rsquo;m fine with it taking a bit longer, as long as I have enough I/O bandwidth left for other stuff).&lt;/p&gt;</description><link>https://ayende.com/blog/174659/the-guts-n-glory-of-database-internals-what-the-disk-can-do-for-you?Key=a45f8a61-1ec2-45df-947f-cb31d642c9b6</link><guid>https://ayende.com/blog/174659/the-guts-n-glory-of-database-internals-what-the-disk-can-do-for-you?Key=a45f8a61-1ec2-45df-947f-cb31d642c9b6</guid><pubDate>Mon, 18 Jul 2016 09:00:00 GMT</pubDate></item><item><title>The Guts n’ Glory of Database Internals: The curse of old age…</title><description>&lt;p&gt;&lt;img style="float: right; display: inline;" src="https://lh3.ggpht.com/yuParR4xtquU5f2wJQPwQ5XVN2iMzbCg-GTCGnm7k_RrID3sDNdp_hNTidUv6i94NAk=w300" alt="" align="right" /&gt;This is a fun series to write, but I&amp;rsquo;m running out of topics where I can speak about the details at a high level without getting into nitty gritty details that will make no sense to anyone but database geeks. If you have any suggestions for additional topics, I would love to hear about them.&lt;/p&gt;
&lt;p&gt;This post, however, is about another aspect of running a database engine. It is all about knowing what is actually going on in the system. A typical web application has very little state (maybe some caches, but that is pretty much about it) and can be fairly easily restarted if you run into some issue (memory leak, fragmentation, etc) to recover most problems while you investigate exactly what is going on. A surprising number of production systems actually have this feature that they just restart on a regular basis, for example. IIS will restart a web application every 29 hours, for example, and I have seen production deployment of serious software where the team was just unaware of that. It did manage to reduce a lot of the complexity, because the application never got around to live long enough to actually carry around that much garbage.&lt;/p&gt;
&lt;p&gt;A database tend to be different. A database engine lives for a very long time, typically weeks, months or years, and it is pretty bad when it goes down, it isn&amp;rsquo;t a single node in the farm that is temporarily missing or slow while it is filling the cache, this is the entire system being down without anything that you can do about it (note, I&amp;rsquo;m talking about single node systems here, distributed systems has high availability systems that I&amp;rsquo;m ignoring at the moment). That tend to give you a very different perspective on how you work.&lt;/p&gt;
&lt;p&gt;For example, if you are using are using Cassandra, it (at least used to) have an issue with memory fragmentation over time. It would still have a lot of available memory, but certain access pattern would chop that off into smaller and smaller slices, until just managing the memory at the JVM level caused issues. In practice, this can cause very long GC pauses (multiple minutes). And before you think that this is an situation unique to managed databases, Redis is known to suffer from fragmentation as well, which can lead to higher memory usage (and even kill the process, eventually) for pretty much the same reason.&lt;/p&gt;
&lt;p&gt;Databases can&amp;rsquo;t really afford to use common allocation patterns (so no malloc / free or the equivalent) because they tend to hold on to memory for a lot longer, and their memory consumption is almost always dictated by the client. In other words, saving increasing large record will likely cause memory fragmentation, which I can then utilize further by doing another round of memory allocations, slightly larger than the round before (forcing even more fragmentation, etc).&amp;nbsp; Most databases use dedicated allocators (typically some from of arena allocators) with limits that allows them to have better control of that and mitigate that issue. (For example, by blowing the entire arena on failure and allocating a new one, which doesn&amp;rsquo;t have any fragmentation).&lt;/p&gt;
&lt;p&gt;But you actually need to build this kind of thing directly into the engine, and you need to also &lt;em&gt;account&lt;/em&gt; for that. When you have a customer calling with &amp;ldquo;why is the memory usage going up&amp;rdquo;, you need to have some way to inspect this and figure out what to do about that. Note that we aren&amp;rsquo;t talking about memory &lt;em&gt;leaks&lt;/em&gt;, we are talking about when everything works properly, just not in the ideal manner.&lt;/p&gt;
&lt;p&gt;Memory is just one aspect of that, if one that is easy to look at. Other things that you need to watch for is anything that has a linear cost proportional to your runtime. For example, if you have a large LRU cache, you need to make sure that after a couple of months of running, pruning that cache isn&amp;rsquo;t going to be an O(N) job running every 5 minutes, never finding anything to prune, but costing a lot of CPU time. The number of file handles is also a good indication of a problem in some cases, some databases engines have a &lt;em&gt;lot&lt;/em&gt; of files open (typically LSM ones), and they can accumulate over time until the server is running out of those.&lt;/p&gt;
&lt;p&gt;Part of the job of the database engine is to consider not only what is going on now, but how to deal with (sometimes literally) abusive clients that try to do very strange things, and how to manage to handle them. In one particular case, a customer was using a feature that was designed to have a maximum of a few dozen entries in a particular query to pass 70,000+ entries. The amazing thing that this worked, but as you can imagine, all sort of assumptions internal to the that features were very viciously violated, requiring us to consider whatever to have a hard limit on this feature, so it is within its design specs or try to see if we can redesign the entire thing so it can handle this kind of load.&lt;/p&gt;
&lt;p&gt;And the most &amp;ldquo;fun&amp;rdquo; is when those sort of bugs are only present after a couple of weeks of harsh production systems running. So even when you know what is causing this, actually reproducing the scenario (you need memory fragmented in a certain way, and a certain number of cache entries, and the application requesting a certain load factor) can be incredibly hard.&lt;/p&gt;</description><link>https://ayende.com/blog/174657/the-guts-n-glory-of-database-internals-the-curse-of-old-age?Key=fce8514a-1f19-4143-8271-f6372afd3103</link><guid>https://ayende.com/blog/174657/the-guts-n-glory-of-database-internals-the-curse-of-old-age?Key=fce8514a-1f19-4143-8271-f6372afd3103</guid><pubDate>Fri, 15 Jul 2016 09:00:00 GMT</pubDate></item><item><title>The Guts n’ Glory of Database Internals: Backup, restore and the environment…</title><description>&lt;p&gt;&lt;img style="float: right; display: inline;" src="https://tctechcrunch2011.files.wordpress.com/2009/01/why-backup.jpg" alt="" align="right" /&gt;&lt;/p&gt;
&lt;p&gt;A lot of the complexities involved in actually building a database engine aren&amp;rsquo;t related to the core features that you want to have. They are related to what looks like peripheral concerns. Backup / restore is an excellent example of that.&lt;/p&gt;
&lt;p&gt;Obviously, you want your database engine to have support for backup and restore. But at the same time, actually implementing that (efficiently and easily) is not something that can be done trivially. Let us consider Redis as a good example; in order to backup its in memory state, Redis will fork a new process, and use the OS&amp;rsquo;s support for copy on write to have a stable snapshot of the in-memory state that it can then write to disk. It is a very elegant solution, with very little code, and it can be done with the assistance of the operation system (almost always a good thing).&lt;/p&gt;
&lt;p&gt;It also exposes you to memory bloat if you are backing up to a slow disk (for example, a remote machine) at the same time that you have a lot of incoming writes. Because the OS will create a copy of every memory page that is touched as long as the backup process is running (on its own copy of the data), the amount of memory actually being used is non trivial. This can lead to swapping, and in certain cases, the OS can decide that it runs out of memory and just start killing random processes (most likely, the actual Redis server that is being used, since it is the consumer of all this memory).&lt;/p&gt;
&lt;p&gt;Another consideration to have is exactly what kind of work do you have to do when you restore the data. Ideally, you want to be up and running as soon as possible. Given database sizes today, even reading the entire file can be prohibitively&amp;nbsp;expensive, so you want to be able to read just enough to start doing the work, and then complete the process of finishing up the restore later (while being online). The admin will appreciate it much more than some sort of a spinning circle or a progress bar measuring how long the system is going to be down.&lt;/p&gt;
&lt;p&gt;The problem with implementing such features is that you need to consider the operating environment in which you are working. The ideal case is if you can control such behaviors, for example, have dedicated backup &amp;amp; restore commands that the admin will use exclusively. But in many cases, you have admins that do all sorts of various things,&amp;nbsp;from shutting down the database and zipping&amp;nbsp;to a shared folder on a nightly basis to taking a snapshot or just running a script with copy / rsync on the files on some schedule.&lt;/p&gt;
&lt;p&gt;Some backup products have support for taking a snapshot of the disk state at a particular point in time, but this goes back to the issues we raised in a &lt;a href="https://ayende.com/blog/174593/the-guts-n-glory-of-database-internals-the-enemy-of-thy-database-is?key=0159e06f67094619a24773d3046cf485"&gt;previous post&lt;/a&gt; about low level hooks. You need to be aware of the relevant costs and implications of those things. In particular, most databases are pretty sensitive to the &lt;em&gt;order&lt;/em&gt; in which you backup certain files. If you take a snapshot of the journal file at time T1, but the data file at time T2, you are likely to have data corruption when you restore (the data file contains data that isn&amp;rsquo;t in the journal, and there is no way to recover it).&lt;/p&gt;
&lt;p&gt;The really bad thing about this is that it is pretty easy for this to &lt;em&gt;mostly&lt;/em&gt; work, so even if you have a diligent admin who test the restore, it might actually work, except when it will fail when you really need it.&lt;/p&gt;
&lt;p&gt;And don&amp;rsquo;t get me started on cloud providers / virtual hosts that offer snapshots. The kind of snapshotting capabilities that a database requires are&amp;nbsp;very specific (all changes that were committed to disk, in the order they were sent, without any future things that might be in flight) in order to be able to successfully backup &amp;amp; restore. From experience, those are not the kind of promises that you get from those tools.&lt;/p&gt;</description><link>https://ayende.com/blog/174625/the-guts-n-glory-of-database-internals-backup-restore-and-the-environment?Key=26ffc44e-9ba1-4a7c-bdc4-4ab7d5a84a51</link><guid>https://ayende.com/blog/174625/the-guts-n-glory-of-database-internals-backup-restore-and-the-environment?Key=26ffc44e-9ba1-4a7c-bdc4-4ab7d5a84a51</guid><pubDate>Thu, 14 Jul 2016 09:00:00 GMT</pubDate></item><item><title>Fast transaction log: Windows</title><description>&lt;p&gt;&lt;br /&gt;In my previous post, I have &lt;a href="https://ayende.com/blog/174753/fast-transaction-log-linux?key=273aa566963445188a9c1c5ef3463311"&gt;tested journal writing techniques on Linux&lt;/a&gt;, in this post, I want to do the same for Windows, and see what the impact of the various options are the system performance.&lt;/p&gt;
&lt;p&gt;Windows has slightly different options than Linux. In particular, in Windows, the various flags and promises and very clear, and it is quite easy to figure out what is it that you are supposed to do.&lt;/p&gt;
&lt;p&gt;We have tested the following scenarios&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doing buffered writes (pretty useless for any journal file, which needs to be reliable, but good baseline metric).&lt;/li&gt;
&lt;li&gt;Doing buffered writes and calling FlushFileBuffers after each transaction (which is pretty common way to handle committing to disk in databases), and the equivalent of calling fsync.&lt;/li&gt;
&lt;li&gt;Using FILE_FLAG_WRITE_THROUGH flag and asking the kernel to make sure that after every write, everything will be flushed to disk. Note that the disk may or may not buffer things.&lt;/li&gt;
&lt;li&gt;Using FILE_FLAG_NO_BUFFERING flag to bypass the kernel&amp;rsquo;s caching and go directly to disk. This has special memory alignment considerations&lt;/li&gt;
&lt;li&gt;Using FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING flag to ensure that we don&amp;rsquo;t do any caching, and actually force the disk to do its work. On Windows, this is guaranteed to ask the disk to flush to persisted medium (but the disk can ignore this request).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;script src="https://gist.github.com/ayende/d1ee28b2f15cf1754304feb8e84d1f27.js"&gt;&lt;/script&gt;
&lt;/blockquote&gt;
&lt;p&gt;We have tested this on an AWS macine ( i2.2xlarge &amp;ndash; 61 GB, 8 cores, 2x 800 GB SSD drive, 1GB /sec EBS), which was running Microsoft Windows Server 2012 R2 RTM 64-bits. The code was compiled for 64 bits with the default release configuration.&lt;/p&gt;
&lt;p&gt;What we are doing is write 1 GB journal file, simulating 16 KB transactions and simulating 65,535 separate commits to disk. That is a &lt;em&gt;lot&lt;/em&gt; of work that needs to be done.&lt;/p&gt;
&lt;p&gt;First, again, I run it on the system drive, to compare how it behaves:&lt;/p&gt;
&lt;table border="0" width="534" cellspacing="0" cellpadding="2"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign="top" width="264"&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/td&gt;
&lt;td valign="top" width="148"&gt;&lt;strong&gt;Time (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td valign="top" width="120"&gt;&lt;strong&gt;Write cost (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="264"&gt;Buffered&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;396&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="120"&gt;
&lt;p align="right"&gt;0.006&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="264"&gt;Buffered + FlushFileBuffers&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;121,403&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="120"&gt;
&lt;p align="right"&gt;1.8&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="263"&gt;FILE_FLAG_WRITE_THROUGH&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;58,376&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="121"&gt;
&lt;p align="right"&gt;0.89&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="263"&gt;FILE_FLAG_NO_BUFFERING&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;56,162&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="121"&gt;
&lt;p align="right"&gt;0.85&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="263"&gt;FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING&lt;/td&gt;
&lt;td valign="top" width="167"&gt;
&lt;p align="right"&gt;55,432&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="152"&gt;
&lt;p align="right"&gt;0.84&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Remember, this is us running on the &lt;em&gt;system disk&lt;/em&gt;, not on the SSD drive. Here are those numbers, which are much more interesting for us.&lt;/p&gt;
&lt;table border="0" width="534" cellspacing="0" cellpadding="2"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign="top" width="264"&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/td&gt;
&lt;td valign="top" width="148"&gt;&lt;strong&gt;Time (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td valign="top" width="120"&gt;&lt;strong&gt;Write cost (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="264"&gt;Buffered&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;410&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="120"&gt;
&lt;p align="right"&gt;0.006&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="264"&gt;Buffered + FlushFileBuffers&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;21,077&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="120"&gt;
&lt;p align="right"&gt;0.321&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="263"&gt;FILE_FLAG_WRITE_THROUGH&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;10,029&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="121"&gt;
&lt;p align="right"&gt;0.153&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="263"&gt;FILE_FLAG_NO_BUFFERING&lt;/td&gt;
&lt;td valign="top" width="148"&gt;
&lt;p align="right"&gt;8,491&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="121"&gt;
&lt;p align="right"&gt;0.129&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="263"&gt;FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING&lt;/td&gt;
&lt;td valign="top" width="167"&gt;
&lt;p align="right"&gt;8,378&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="152"&gt;
&lt;p align="right"&gt;0.127&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And those numbers are very significant. Unlike the system disk, where we basically get whatever spare cycles we have, in both Linux and Windows, the SSD disk provides really good performance. But even on identical machine, running nearly identical code, there are significant performance differences between them.&lt;/p&gt;
&lt;p&gt;Let me draw it out to you:&lt;/p&gt;
&lt;table border="0" width="551" cellspacing="0" cellpadding="2"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign="top" width="225"&gt;
&lt;p align="center"&gt;&lt;strong&gt;Options&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="131"&gt;
&lt;p align="center"&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="109"&gt;
&lt;p align="center"&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="84"&gt;
&lt;p align="center"&gt;&lt;strong&gt;Difference&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="227"&gt;
&lt;p align="center"&gt;Buffered&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="131"&gt;
&lt;p align="center"&gt;0.006&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="108"&gt;
&lt;p align="center"&gt;0.03&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="83"&gt;
&lt;p align="center"&gt;80% Win&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="228"&gt;
&lt;p align="center"&gt;Buffered + fsync() / FlushFileBuffers()&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="131"&gt;
&lt;p align="center"&gt;0.32&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="108"&gt;
&lt;p align="center"&gt;0.35&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="83"&gt;
&lt;p align="center"&gt;9% Win&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="229"&gt;
&lt;p align="center"&gt;O_DSYNC / FILE_FLAG_WRITE_THROUGH&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="131"&gt;
&lt;p align="center"&gt;0.153&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="108"&gt;
&lt;p align="center"&gt;0.29&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="83"&gt;
&lt;p align="center"&gt;48% Win&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="229"&gt;
&lt;p align="center"&gt;O_DIRECT / FILE_FLAG_NO_BUFFERING&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="131"&gt;
&lt;p align="center"&gt;0.129&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="108"&gt;
&lt;p align="center"&gt;0.14&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="83"&gt;
&lt;p align="center"&gt;8% Win&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign="top" width="229"&gt;
&lt;p align="center"&gt;O_DIRECT | O_DSYNC / FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="131"&gt;
&lt;p align="center"&gt;0.127&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="118"&gt;
&lt;p align="center"&gt;0.31&lt;/p&gt;
&lt;/td&gt;
&lt;td valign="top" width="112"&gt;
&lt;p align="center"&gt;60% Win&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In pretty much all cases Windows has been able to out perform Linux on this specific scenario. In many cases by a significant margin. In particular, in the scenario that I actually really care about, we see 60% performance advantage to Windows.&lt;/p&gt;
&lt;p&gt;One of the reasons for this blog post and the detailed code and scenario is the external verification of these numbers. I&amp;rsquo;ll love to know that I missed something that would make Linux speed comparable to Windows, because right now this is pretty miserable.&lt;/p&gt;
&lt;p&gt;I do have a hunch about those numbers, though. SQL Server is a &lt;em&gt;major&lt;/em&gt; business for Microsoft, so they have a lot of pull in the company. And SQL Server uses FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING internally to handle the transaction log it uses. Like quite a bit of other Win32 APIs (WriteGather, for example), it looks tailor made for database journaling. I&amp;rsquo;m guessing that this code path has been gone over multiple times over the years, trying to optimize SQL Server by smoothing anything in the way.&lt;/p&gt;
&lt;p&gt;As a result, if you know what you are doing, you can get some really impressive numbers on Windows in this scenario. Oh, and just to quite the nitpickers:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ayende.com/blog/Images/Open-Live-Writer/Fast-transaction-log-Windows_BC9F/image_thumb[5]_2.png"&gt;&lt;img style="background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border: 0px;" title="image_thumb[5]" src="https://ayende.com/blog/Images/Open-Live-Writer/Fast-transaction-log-Windows_BC9F/image_thumb[5]_thumb.png" alt="image_thumb[5]" width="515" height="582" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;</description><link>https://ayende.com/blog/174785/fast-transaction-log-windows?Key=640a630c-7cec-4eb6-86df-84fccc071fff</link><guid>https://ayende.com/blog/174785/fast-transaction-log-windows?Key=640a630c-7cec-4eb6-86df-84fccc071fff</guid><pubDate>Wed, 13 Jul 2016 09:00:00 GMT</pubDate></item><item><title>Fast transaction log: Linux</title><description>&lt;p&gt;&lt;img style="float: right; display: inline" src="http://www.dalegladstone.com/digital/tuxrun/tuxrun.jpg" width="194" align="right" height="240"&gt;We were doing some perf testing recently, and we got some odd results when running a particular benchmark on Linux. So we decided to check this on a much deeper level.&lt;/p&gt; &lt;p&gt;We got an AWS macine ( i2.2xlarge – 61 GB, 8 cores, 2x 800 GB SSD drive, running Ubuntu 14.04, using kernel version 3.13.0-74-generic, 1GB/sec EBS drives ) and run the following code and run it. This tests the following scenarios on a 1GB file (pre allocated) and “committing” 65,536 (64K) transactions with 16KB of data in each. The idea is that we are testing how fast we can create write those transactions to the journal file, so we can consider them committed.&lt;/p&gt; &lt;p&gt;We have tested the following scenarios&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Doing buffered writes (pretty useless for any journal file, which needs to be reliable, but good baseline metric).  &lt;li&gt;Doing buffered writes and calling fsync after each transaction (which is pretty common way to handle committing to disk in databases)  &lt;li&gt;Doing buffered writes and calling fdatasync (which is supposed to be slightly more efficient than calling fsync in this scenario).  &lt;li&gt;Using O_DSYNC flag and asking the kernel to make sure that after every write, everything will be synced to disk.  &lt;li&gt;Using O_DIRECT flag to bypass the kernel’s caching and go directly to disk.  &lt;li&gt;Using O_DIRECT | O_DSYNC flag to ensure that we don’t do any caching, and actually force the disk to do its work. &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;The code is written in C, and it is written to be pretty verbose and ugly. I apologize for how it looks, but the idea was to get some useful data out of this, not to generate beautiful code. It is only quite probable that I made some mistake in writing the code, which is partly why I’m talking about this.&lt;/p&gt; &lt;p&gt;Here is the code, and the results of execution are below:&lt;/p&gt; &lt;blockquote&gt;&lt;script src="https://gist.github.com/ayende/5565f8a9b50c03484160e1900afbe524.js"&gt;&lt;/script&gt;&lt;/blockquote&gt; &lt;p&gt;It was compiled using: &lt;em&gt;gcc journal.c –o3 –o run &amp;amp;&amp;amp; ./run&lt;/em&gt;&lt;/p&gt; &lt;p&gt;The results are quite interesting:&lt;/p&gt; &lt;table cellspacing="0" cellpadding="2" width="534" border="0"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top" width="148"&gt;&lt;strong&gt;Time (ms)&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt;&lt;strong&gt;Write cost (ms)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;Buffered&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;525&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt; &lt;p align="right"&gt;0.03&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;Buffered + fsync&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;72,116&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt; &lt;p align="right"&gt;1.10&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;Buffered + fdatasync&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;56,227&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt; &lt;p align="right"&gt;0.85&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="263"&gt;O_DSYNC&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;48,668&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="121"&gt; &lt;p align="right"&gt;0.74&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="263"&gt;O_DIRECT&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;47,065&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="121"&gt; &lt;p align="right"&gt;0.71&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="263"&gt;O_DIRECT | O_DSYNC&lt;/td&gt; &lt;td valign="top" width="167"&gt; &lt;p align="right"&gt;47,877&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="152"&gt; &lt;p align="right"&gt;0.73&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;The results are quite interesting. The buffered call, which is useless for a journal, but important as something to compare to. The rest of the options will ensure that the data reside on disk* after the call to write, and are suitable to actually get safety from the journal.&lt;/p&gt; &lt;p&gt;* The caveat here is the use of O_DIRECT, the docs (and Linus) seems to be pretty much against it, and there are few details on how this works with regards to instructing the disk to actually bypass all buffers. O_DIRECT | O_DSYNC seems to be the recommended option, but that probably deserve more investigation.&lt;/p&gt; &lt;p&gt;Of course, I had this big long discussion on the numbers planned. And then I discovered that I was actually running this on the &lt;em&gt;boot&lt;/em&gt; disk, and not one of the SSD drives. That was a face palm of epic proportions that I was only saved from by the problematic numbers that I was seeing.&lt;/p&gt; &lt;p&gt;Once I realized what was going on and fixed that, we got very different numbers.&lt;/p&gt; &lt;table cellspacing="0" cellpadding="2" width="534" border="0"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top" width="148"&gt;&lt;strong&gt;Time (ms)&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt;&lt;strong&gt;Write cost (ms)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;Buffered&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;522&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt; &lt;p align="right"&gt;0.03&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;Buffered + fsync&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;23,094&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt; &lt;p align="right"&gt;0.35&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="264"&gt;Buffered + fdatasync&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;23,022&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="120"&gt; &lt;p align="right"&gt;0.35&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="263"&gt;O_DSYNC&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;19,555&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="121"&gt; &lt;p align="right"&gt;0.29&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="263"&gt;O_DIRECT&lt;/td&gt; &lt;td valign="top" width="148"&gt; &lt;p align="right"&gt;9,371&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="121"&gt; &lt;p align="right"&gt;0.14&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="263"&gt;O_DIRECT | O_DSYNC&lt;/td&gt; &lt;td valign="top" width="167"&gt; &lt;p align="right"&gt;20,595&lt;/p&gt;&lt;/td&gt; &lt;td valign="top" width="152"&gt; &lt;p align="right"&gt;0.31&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;There is about 10% difference between fsync and fdatasync when using the HDD, but there is barely any difference as far as the SSD is concerned. This is because the SSD can do random updates (such as updating both the data and the metadata) much faster, since it doesn’t need to move the spindle.&lt;/p&gt; &lt;p&gt;Of more interest to us is that D_SYNC is significantly faster in both SSD and HHD. About 15% can be saved by using it.&lt;/p&gt; &lt;p&gt;But I’m just drolling over O_DIRECT write profile performance on SSD. That is so good, unfortunately, it isn’t safe to do. We need both it and O_DSYNC to make sure that we only get confirmation on the write when it hits the physical disk, instead of the drive’s cache (that is why it is actually faster, we are writing directly to the disk’s cache, basically).&lt;/p&gt; &lt;p&gt;The tests were run using ext4. I played with some options (noatime, noadirtime, etc), but there wasn’t any big difference between them that I could see.&lt;/p&gt; &lt;p&gt;In my next post, I’ll test the same on Windows.&lt;/p&gt;</description><link>https://ayende.com/blog/174753/fast-transaction-log-linux?Key=273aa566-9634-4518-8a9c-1c5ef3463311</link><guid>https://ayende.com/blog/174753/fast-transaction-log-linux?Key=273aa566-9634-4518-8a9c-1c5ef3463311</guid><pubDate>Tue, 12 Jul 2016 09:00:00 GMT</pubDate></item></channel></rss>