linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Daniel Phillips <phillips@arcor.de>
To: unlisted-recipients:; (no To-header on input)
Cc: "Theodore Ts'o" <tytso@mit.edu>,
	Andreas Dilger <adilger@clusterfs.com>,
	Christopher Li <chrisl@vmware.com>,
	Alex Tomas <bzzz@tmi.comex.ru>
Subject: [RFC] Improved inode number allocation for HTree
Date: Mon, 10 Mar 2003 21:45:51 +0100	[thread overview]
Message-ID: <20030310204146.875D3F0D4D@mx12.arcor-online.net> (raw)
In-Reply-To: <m3u1ecl5h8.fsf@lexa.home.net>

Summary of the problem:

HTree's random relationship between directory entry order and inode order has 
been observed to cause increased cache pressure, affecting performance under 
certain conditions.  Most of this pressure is due to inode table usage, which 
is normally several times larger than the directory itself.  Even when the 
inode table usage is much less than available cache, it may still exceed the 
size of the journal file (8 MB by default).  Though the journal only becomes 
involved when blocks are modified, unfortunately, because of atime updates, 
this includes all directory operations.  We could suggest to users that they 
should disable atime updating if they care about performance, but we ought to 
be able to do better than that.

To reduce this cache pressure, what we need to do is make the directory entry 
order correspond more closely to the inode number order.  It doesn't have to 
correspond perfectly in order to show substantial improvement, as Tomas 
demonstrated with his recent getdents sorting patch.

Analysis:

To understand why sorting relatively small sections of the directory entries 
reduces the cache pressure, we can think about the lifetime of an inode table 
block over the course of a directory traversal.  This traversal lifetime 
extends from when it is first accessed (always a write as noted above) to 
when it is last accessed, or the directory traversal finishes.  Each inode 
table block contains multiple inodes, so if those inodes are accessed 
randomly, the traversal lifetime of every inode table block will tend to be 
from nearly the beginning of the traversal to nearly the end, and so the load 
on the cache will be nearly the same as the number of inode table blocks.  If 
this load is greater than available cache, various blocks will have to be 
written out and reloaded later for further processing.  We can call the 
period between loading and evicting an inode table block, its "cache period".

The number of times a block must be written/reloaded depends not only on the 
number of inode table blocks covered by the directory and the availablity of 
cache memory, but on the probable number of the inodes within the block that 
will be written during each of the block's cache period.  By sorting the 
directory chunkwise, the probable number of inodes processed in one cache 
period increases, reducing the number of cache periods, in turn reducing disk 
seeking and transfer overhead.

To apply the above model to the journal, just substitute "journal period" for 
"cache period", where journal period is defined as the time from when a dirty 
block first enters the journal to when it is written out to disk. 

We can see that local sorting or directory chunks has hardly any effect on 
the traversal lifetime of a block; it only reduces the number of cache 
periods.  The size of the chunk of directory that we sort locally presumably 
stays constant, so the probable number of inodes on each inode table block 
processed in each cache period approaches one as the directory size 
increases.  At some point, there is no longer any benefit from sorting, andwe 
arrive at that point at rather realistic directory sizes.

We can always do the job perfectly by sorting the whole directory.  This 
would require changes in every getdents-using application that cares about 
performance, to either make the getdents window as big as the directory or to 
do the sort in user space.  A spinoff benefit of doing the full sort is that 
it becomes trivial to detect and eliminate duplicate entries.  Higher memory 
load is a drawback, as is the work required to update user space programs.  
There will also be noticable latency introduced by the sort, which didn't 
appear before.  If sorting is to be done in user space then we impose the 
requirement on all filesystems - even those that do not use inode numbers 
internally - that they supply an inode key for sorting, and that the supplied 
sorting key be meaningful in terms of cache locality.

Proposed Solution:

What if we could maintain the inodes in the inode table in the same order as 
the directory entries, permanently, so that no sorting is required?  If we 
could do this, the traversal lifetime of each inode table block would be the 
same as the number of inodes it contains, and the cache load due to the inode 
table would be exactly one block.  That is, the directory traversal would 
process each inode table block completely before moving on to the next one. 
Though the current Ext3 filesystem structure does not support this directly, 
we can approximate the effect quite closely within the existing framework.

Due to Ted's recent work, we are already traversing the directory in hash 
order, as opposed to physical allocation order, because of the particular 
approach adopted to address the telldir/seekdir problem.  This is relatively 
inexpensive.  So now, what we would like to do is make the inode number of 
each directory entry increase monotonically, corresponding to monotonically 
increasing hash values.  We also want to allocate those inode numbers densely 
in the inode table, to minimize seeking.  If we know beforehand how many 
entries the directory will have, this is easy: when we create an entry, we 
multiply its hash value by the total number of entries divided by the total 
hash range (2**31), add in a base target, and use that as the goal to 
allocate a new inode.  Due to the randomness of the hash value, we will 
sometimes be forced to allocate an inode number out of order, but hopefully 
there won't be too many of those, and they won't be very far out of order.  
We may also end up with some gaps in the inode table.  In fact, we may 
intentionally leave some slack in order to reduce the number of out-of-order 
allocations.  These problems are not serious.

The big problem is that we don't know the number of directory entries ahead 
of time.  To get around this, we can use the current size of the directory, 
rounded up to a power of two as the size estimate.  Each time the directory
increases to a new binary size we establish a new region in the inode table, 
which will map to the to-be-created half of directory entries, roughly in 
order.

Now, consider what happens to a group of directory entries created when the 
directory was so small that it mapped to a single inode table block.  After a 
series of leaf splits, these former neighbors will end up far apart in the 
hash order, but their inode numbers will still be ordered monotonically over 
a traversal.  The traversal lifetime of that group of entries will be nearly 
the entire directory traversal, but there is only one such block.

When the directory became larger, some newly created entries were mapped to 
two new inode table blocks, the traversal lifetimes of which are roughly half 
the entire traversal.  There are only two such blocks, and their lifetimes do 
not overlap, so these two blocks only generate one block worth of cache load.

So it goes, with each new set of entries of size 2**i generating only one 
additional block of cache load.  The final cache load is O log2(N), a 
pleasingly small number.

A very nice property of this inode allocation strategy is that the ordering 
will tend to be reinforced as a directory ages, as opposed to slowly decaying 
towards randomness as happens with linear directories.

So this approach is attractive, especially as it can be implemented in a 
small number of lines of code.  The main outstanding issue I have not 
addressed is how to arrange things so that the inode allocation patterns of 
neighbouring directories don't interfere with each other too much.  I expect 
that each time the directory grows to a new power of two size, we need to 
scan the inode allocation map for a relatively unpopulated region of that 
size and store the region's base in the directory header.  Would we need to 
store the entire history of allocation bases?  Probably not.  This aspect 
requires further consideration.

Regards,

Daniel


  parent reply	other threads:[~2003-03-10 20:31 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2003-02-27 17:31 [Bug 417] New: htree much slower than regular ext3 Martin J. Bligh
2003-02-28  2:55 ` Daniel Phillips
2003-02-27 21:00   ` Andreas Dilger
2003-02-28  4:12     ` Daniel Phillips
2003-02-27 21:33       ` Martin J. Bligh
2003-03-13 21:04     ` [Ext2-devel] " Stephen C. Tweedie
2003-03-07 15:46 ` Alex Tomas
2003-03-08 17:38   ` Daniel Phillips
2003-03-07 23:27     ` Theodore Ts'o
2003-03-09 19:26       ` Alex Tomas
2003-03-09  7:08     ` Alex Tomas
2003-03-10 17:58       ` Daniel Phillips
2003-03-10 21:25       ` Theodore Ts'o
2003-03-11 21:57   ` Bill Davidsen
     [not found] ` <20030307214833.00a37e35.akpm@digeo.com>
     [not found]   ` <20030308010424.Z1373@schatzie.adilger.int>
2003-03-09 22:54     ` [Ext2-devel] " Daniel Phillips
2003-03-08 23:19       ` Andrew Morton
2003-03-09 23:10   ` Daniel Phillips
     [not found] ` <20030309184755.ACC80FCA8C@mx12.arcor-online.net>
     [not found]   ` <m3u1ecl5h8.fsf@lexa.home.net>
2003-03-10 20:45     ` Daniel Phillips [this message]
     [not found]       ` <3E6D1D25.5000004@namesys.com>
     [not found]         ` <20030311031216.8A31CEFD5F@mx12.arcor-online.net>
2003-03-11 10:45           ` [RFC] Improved inode number allocation for HTree Hans Reiser
2003-03-11 13:00             ` Helge Hafting
2003-03-11 13:41               ` Daniel Phillips
2003-03-11 17:16                 ` Andreas Dilger
2003-03-11 19:39                 ` Helge Hafting
2003-03-11 20:19                   ` Daniel Phillips
2003-03-11 21:25                 ` atomic kernel operations are very tricky to export to user space (was [RFC] Improved inode number allocation for HTree ) Hans Reiser
2003-03-11 23:49                   ` Jamie Lokier
2003-03-10 20:48     ` [RFC] Improved inode number allocation for HTree Daniel Phillips
2003-03-10 21:04       ` John Bradford
2003-03-10 21:28         ` Andreas Schwab
2003-03-10 21:50           ` Filesystem write priorities, (Was: Re: [RFC] Improved inode number allocation for HTree) John Bradford
2003-03-14 21:55             ` [Ext2-devel] " Stephen C. Tweedie
2003-03-10 21:33         ` [RFC] Improved inode number allocation for HTree Daniel Phillips
2003-03-10 21:47           ` [Ext2-devel] " Bryan O'Sullivan
2003-03-10 22:02             ` Matthew Wilcox
2003-03-11  8:47               ` Jakob Oestergaard
2003-03-11 11:27                 ` John Bradford
2003-03-14 21:57               ` Stephen C. Tweedie
2003-03-15  8:39                 ` jw schultz
     [not found] <20030311194014$426e@gated-at.bofh.it>
     [not found] ` <20030311194014$1a3c@gated-at.bofh.it>
     [not found]   ` <20030311194014$78c3@gated-at.bofh.it>
     [not found]     ` <20030311194014$5811@gated-at.bofh.it>
     [not found]       ` <20030311194014$49a6@gated-at.bofh.it>
2003-03-11 20:14         ` Pascal Schmidt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20030310204146.875D3F0D4D@mx12.arcor-online.net \
    --to=phillips@arcor.de \
    --cc=adilger@clusterfs.com \
    --cc=bzzz@tmi.comex.ru \
    --cc=chrisl@vmware.com \
    --cc=tytso@mit.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).