home *** CD-ROM | disk | FTP | other *** search
/ DOS/V Power Report 2004 March / VPR0403.ISO / talks / 273 / paper.dkb < prev    next >
Encoding:
Extensible Markup Language  |  2003-09-18  |  48.6 KB  |  1,130 lines

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2.  
  3. <article id="paper">
  4. <articleinfo>
  5.   <title>Atomic Filesystems: Going Beyond Journaling</title>
  6.   <author>
  7.     <firstname>Hans</firstname>
  8.     <surname>Reiser</surname>
  9.     <othername>reiser@namesys.com</othername>
  10.   </author>  
  11.  
  12.   <author>
  13.     <firstname>Vladimir</firstname>
  14.     <surname>Saveliev</surname>
  15.     <othername>vs@namesys.com</othername>
  16.   </author>
  17.   <author>
  18.     <firstname>Vladimir</firstname>
  19.     <surname>Saveliev</surname>
  20.     <othername>vs@namesys.com</othername>
  21.   </author>
  22.   <author>
  23.     <firstname>Nikita</firstname>
  24.     <surname>Danilov</surname>
  25.     <othername>nikita@namesys.com</othername>
  26.   </author>
  27.   <author>
  28.     <firstname>Alexander</firstname>
  29.     <surname>Zarochentcev</surname>
  30.     <othername>zam@namesys.com</othername>
  31.   </author>
  32.   <author>
  33.     <firstname>Vladimir</firstname>
  34.     <surname>Demidov</surname>
  35.     <othername>demidov@namesys.com</othername>
  36.   </author>
  37.   <author>
  38.     <firstname>Vitaly</firstname>
  39.     <surname>Fertman</surname>
  40.     <othername>vitaly@namesys.com</othername>
  41.   </author>
  42.   <author>
  43.     <firstname>Oleg</firstname>
  44.     <surname>Drokin</surname>
  45.     <othername>green@namesys.com</othername>
  46.   </author>
  47.   <author>
  48.     <firstname>Yury</firstname>
  49.     <surname>Umanetz</surname>
  50.     <othername>umka@namesys.com</othername>
  51.   </author>
  52.   <author>
  53.     <firstname>Edward</firstname>
  54.     <surname>Shushkin</surname>
  55.     <othername>edward@namesys.com</othername>
  56.   </author>
  57.   <author>
  58.     <firstname>Elena</firstname>
  59.     <surname>Gryaznova</surname>
  60.     <othername>grev@namesys.com</othername>
  61.   </author>  <copyright>
  62.     <year>2003</year>
  63.     <holder>Hans Reiser</holder>
  64.   </copyright>
  65. <corpauthor>Namesys</corpauthor>
  66. <corpauthor>This work was sponsored by the Defense Advanced Research Projects Agency of the United States Government (DARPA).  DARPA does not endorse this work, it merely sponsors it.</corpauthor>
  67. </articleinfo>
  68.  
  69. <section>
  70. <title>Abstract</title>
  71. <para>
  72. I will discuss the performance aspects of Reiser4 design that allow us
  73. to both 1) implement filesystem
  74. operations as <emphasis> atomic </emphasis> operations, and 2) increase overall filesystem performance compared to
  75. ReiserFS V3.  I will also suggest that fully atomic filesystems keep
  76. your data more secure than journaling filesystems, and allow application writers greater ease in avoiding certain types of security holes.</para>
  77. </section>
  78.  
  79. <section>
  80. <title>Reducing The Damage of Crashing</title>
  81. <para>
  82. When a computer crashes there is data in RAM which has not reached disk that is
  83. lost.  You might at first be tempted to think that we want to then keep all of
  84. the data that <emphasis>did</emphasis> reach disk.</para>
  85. <para>
  86. Suppose that you were performing a transfer of $10 from bank account A to bank account B, and this consisted of two operations
  87. 1) debit $10 from A, and 2) credit $10 to B.
  88. </para>
  89.  
  90. <para>
  91. Suppose that 1) but not 2) reached disk and the computer crashed.  It would be
  92. better to disregard 1) than to let 1) but not 2) take effect, yes?</para>
  93.  
  94. <para>
  95. When there is a set of operations which we will ensure will all take effect, or none
  96. take effect, we call the set as a whole an <emphasis>atom</emphasis>.  Reiser4
  97. implements all of its filesystem system calls (requests to the kernel to do
  98. something are called <emphasis>system calls </emphasis>)  as fully atomic operations, and
  99. allows one to define new atomic operations using its plugin infrastructure.
  100. Why don't all filesystems do this?  Performance. </para> 
  101.  
  102. <para>Reiser4 possesses employs new
  103. algorithms that allow it to make these operations atomic at little additional
  104. cost where other
  105. filesystems have paid a heavy, usually prohibitive, price to do that.  We hope
  106. to share with you how that is done.</para>
  107. </section>
  108.  
  109. <section>
  110. <title>A Brief History Of How Filesystems Have Handled Crashes</title>
  111.  
  112. <section>
  113. <title>Filesystem Checkers</title>
  114.  
  115. <para> Originally filesystems had filesystem checkers that would run
  116. after every crash.  The problem with this is that 1) the checkers can
  117. not handle every form of damage well, and 2) the checkers run for a
  118. long time.  The amount of data stored on hard drives increased faster
  119. than the transfer rate (the rate at which hard drives transfer their
  120. data from the platter spinning inside them into the computer's RAM
  121. when they are asked to do one large continuous read, or the rate in
  122. the other direction for writes), which means that the checkers took
  123. longer to run, and as the decades ticked by it became less and less
  124. reasonable for a mission critical server to wait for the checker.
  125. </para>
  126. </section>
  127.  
  128. <section><title>Fixed Location Journaling</title>
  129.  
  130. <para>A solution to this was adopted of first writing each atomic
  131. operation to a location on disk called the
  132. <emphasis>journal</emphasis> or <emphasis>log</emphasis>, and then,
  133. only after each atom had fully reached the journal, writing it to the
  134. <emphasis>committed area</emphasis> of the filesystem (in describing
  135. ReiserFS V3 we used to call this the real area, but nobody much liked
  136. that term....).</para>
  137.  
  138. <para>The problem with this is that twice as much
  139. data needs to be written.  On the one hand, if the workload is dominated by seeks, this is not
  140. as much of a burden as one might think.  On the other hand, for writes of large files, it halves
  141. performance because such writes are usually transfer time dominated.  
  142. </para>
  143.  
  144. <para>For this reason, meta-data journaling came to dominate general
  145. purpose usage.  With meta-data journaling, the filesystem guarantees
  146. that all of its operations on its meta-data will be done atomically.
  147. If a file is being written to, the data in that file being written may
  148. be corrupted as a result of non-atomic data operations, but the
  149. filesystem's internals will all be consistent.  The performance
  150. advantage was substantial.  V3 of reiserfs offers both meta-data and
  151. data journaling, and defaults to meta-data journaling because that is
  152. the right solution for most users.  Oddly enough, meta-data journaling
  153. is much more work to implement because it requires being precise about
  154. what needs to be journaled.  As is so often the case in programming,
  155. doing less work requires more code.
  156. </para>
  157.  
  158. <para>
  159. In sum, with fixed location data journaling, the overhead of making
  160. each operation atomic is too high for it to be appropriate for average
  161. applications that don't especially need it --- because of the cost of
  162. writing twice.  Applications that do need atomicity are written to use
  163. fsync and rename to accomplish atomicity, and these tools are simply
  164. terrible for that job.  Terrible in performance, and terrible in the
  165. ugliness they add to the coding of applications.  Stuffing a
  166. transaction into a single file just because you need the transaction
  167. to be atomic is hardly what one would call flexible semantics.  Also,
  168. data journaling, with all its performance cost, still does not
  169. necessarily guarantee that every system call is fully atomic, much
  170. less that one can construct sets of operations that are fully atomic.
  171. It usually merely guarantees that the files will not contain random
  172. garbage, however many blocks of them happen to get written, and
  173. however much the application might view the result as inconsistent
  174. data.  I hope you understand that we are trying to set a new
  175. expectation here for how secure a filesystem should keep your data,
  176. when we provide these atomicity guarantees.  </para>
  177. </section></section>
  178.  
  179. <section>
  180. <title>Wandering Logs</title>
  181. <para>
  182. One way to avoid having to write the data twice is to change one's
  183. definition of where the log area and the committed area are, instead of
  184. moving the data from the log to the committed area.
  185. </para>
  186. <para>
  187. There is an annoying complication to this though, in that there are
  188. probably a number of pointers to the data from the rest of the
  189. filesystem, and we need for them to point to the new data.  When the commit occurs, we need to write those pointers
  190. so that they point to the data we are committing.  Fortunately, these
  191. pointers tend to be highly concentrated as a result of our tree
  192. design.  But wait, if we are going to update those pointers, then we
  193. want to commit those pointers atomically also, which we could do if we
  194. write them to another location and update the pointers to them,
  195. and.... up the tree the changes ripple.  When we get to the top of the
  196. tree, since disk drives write sectors atomically, the block number of
  197. the top can be written atomically into the superblock by the disk
  198. thereby committing everything the new top points to.  This is indeed
  199. the way WAFL, the Write Anywhere File Layout filesystem invented by
  200. Dave Hitz at Network Appliance, works.  It always ripples changes all the way to
  201. the top, and indeed that works rather well in practice, and most of
  202. their users are quite happy with its performance.</para>
  203.  
  204. <section>
  205. <title>Writing Twice May Be Optimal Sometimes</title>
  206. <para>
  207. Suppose that a file is currently well laid out, and you write to a single block in the
  208. middle of it, and you then expect to do many reads of the file.  That is an
  209. extreme case illustrating that sometimes it is worth writing twice so that a
  210. block can keep its current location.  If one does write twice, one does not
  211. need to ripple pointer updates to the top.  If one writes a node twice, one also does not need
  212. to update its parent.  
  213. Our code is a toolkit that can be used to implement different layout
  214. policies, and one of the available choices is whether to write over a block in
  215. its current place, or to relocate it to somewhere else.  I don't think there is
  216. one right answer for all usage patterns.</para>
  217.  
  218. <para>
  219. If a block is adjacent to many other dirty blocks in the tree, then
  220. this decreases the significance of the cost to read performance of
  221. relocating it and its neighbors.  If one knows that a repacker will
  222. run once a week (a repacker is expected for V4.1, and is (a bit oddly)
  223. absent from WAFL), this also decreases the cost of relocation.  After
  224. a few years of experimentation, measurement, and user feedback, we
  225. will say more about our experiences in constructing user selectable
  226. policies.</para>
  227.  
  228. <para>
  229. Do we pay a performance penalty for making Reiser4 atomic?  Yes, we
  230. do.  Is it an acceptable penalty?  This is hard to measure, because we
  231. picked up a lot more performance from other improvements in Reiser4
  232. than we lost to atomicity, but I am fairly confident that the answer
  233. is yes.  If changes are either large or batched together with enough
  234. other changes to become large, the performance penalty is low and
  235. drowned out by other performance improvements.  Scattered small
  236. changes threaten us with read performance losses compared to
  237. overwriting in place and taking our chances with the data's
  238. consistency if there is a crash, but use of a repacker will mostly
  239. alleviate this scenario.  I have to say that in my heart I don't have
  240. any serious doubts that for the general purpose user the increase in
  241. data security is worthwhile.  The users though will have the final
  242. say.</para>
  243.  
  244. </section>
  245.  
  246. <section>
  247. <title>Committing</title>
  248.  
  249. <para>A transaction preserves the previous contents of all modified
  250. blocks in their original location on disk until the transaction
  251. commits, and commit means the transaction has hereby reached a state
  252. where it will be completed even if there is a crash.  </para>
  253.  
  254. <para>
  255. The dirty blocks of an atom (which were captured and subsequently
  256. modified) are divided into two sets, <emphasis>relocate</emphasis> and
  257. <emphasis>overwrite</emphasis>, each of which is preserved in a different
  258. manner. </para>
  259.  
  260. <para>The <emphasis>relocatable</emphasis> set is the set of blocks that have a dirty
  261. parent in the atom. </para>
  262.  
  263. <para>The <emphasis>relocate</emphasis> set is those members of the relocatable set
  264. that will be written to a new or first location rather than
  265. overwritten. </para>
  266.  
  267.  
  268. <para>The <emphasis>overwrite</emphasis> set contains all dirty blocks in the atom that need to
  269. be written to their original locations, which is all those not in the
  270. relocate set.  In practice this is those which do not have a parent we
  271. want to dirty, plus also those for which overwrite is the better layout
  272. policy despite the write twice cost.  Note that the superblock is the parent of the root node
  273. and the free space bitmap blocks have no parent. By these definitions,
  274. the superblock and modified bitmap blocks are always part of the
  275. overwrite set.</para>
  276.  
  277. <para>The <emphasis>wandered set</emphasis> is the set of blocks that the
  278. overwrite set will be written to temporarily until the overwrite set commits.</para>
  279.  
  280. <para>An interesting definition is the <emphasis>minimum overwrite</emphasis> set,
  281. which uses the same definitions as above with the following
  282. modification. If at least two dirty blocks have a common parent that
  283. is clean then its parent is added to the minimum overwrite set. The
  284. parent's dirty children are removed from the overwrite set and placed
  285. in the relocate set. This policy is an example of what will be
  286. experimented with in later versions of Reiser4 using the layout
  287. toolkit. </para>
  288.  
  289. <para>For space reasons, we leave out the full details on exactly when we
  290. relocate vs. overwrite, and the reader should not regret this because
  291. years of experimenting is probably ahead before we can speak with the
  292. authority necessary for a published paper on the effects of the many
  293. details and variations possible.</para>
  294.  
  295. <para>
  296. When we commit we write a wander list which consists of a mapping of the wander
  297. set to the overwrite set.  The wander list is a linked list of blocks
  298. containing pairs of block numbers.  The last act of committing a transaction is to
  299. update the super block to point to the front of that list.  Once that is done,
  300. if there is a crash, the crash recovery will go through that list and "play"
  301. it, which means to write the wandered set over the overwrite set.  If there is
  302. not a crash, we will also play it.</para>
  303.  
  304. <para>
  305. There are many more details of how we handle the deallocation of
  306. wandered blocks, the handling of bitmap blocks, and so forth.  You are
  307. encouraged to read the comments at the top of our source code files (e.g. wander.c) for such
  308. details....  </para>
  309. </section>
  310. </section>
  311.  
  312. <section>
  313. <title>Reiser4's Performance Design</title>
  314.  
  315. <section>
  316. <title>Tree Design Concepts</title>
  317.  
  318. <section>
  319. <title>Height Balancing versus Space Balancing</title>
  320.  
  321. <para> <emphasis>Height Balanced Trees</emphasis> are trees such that
  322. each possible search path from root node to leaf node has exactly the
  323. same length (<emphasis>Length</emphasis> = number of nodes traversed
  324. from root node to leaf node). For instance the height of the tree in
  325. Figure 2 is four while the height of the left hand tree in Figure 1.3
  326. is three and of the single node in figure 1.0 is 1. </para>
  327.  
  328. <para>The term <emphasis>balancing</emphasis> is used for several very
  329. distinct purposes in the balanced tree literature.  Two of the most
  330. common are: to describe balancing the <emphasis>height</emphasis>, and
  331. to describe balancing the <emphasis>space usage</emphasis> within the nodes of the
  332. tree.  These quite different definitions are unfortunately a classic
  333. source of confusion for readers of the literature. </para>
  334.  
  335. <para>
  336. Most algorithms for accomplishing height balancing do so
  337. by only growing the tree at the top. Thus the tree never gets out of
  338. balance.  </para>
  339.  
  340. <para>
  341. <inlinemediaobject>
  342. <imageobject>
  343. <imagedata fileref="../treepics/treepicswin/Unbalanced_tree.gif" format="GIF"/>
  344. </imageobject>
  345. <textobject>
  346. <para>
  347. This is a 4 level unbalanced tree with fanout N = 3 that has then lost
  348. some nodes to deletions and needs to be balanced
  349. </para>
  350. </textobject>
  351. </inlinemediaobject>
  352. </para>
  353.  
  354. <para>
  355. Fig. 1.11. This is an <emphasis>unbalanced tree</emphasis>. It could
  356. have originally been balanced and then lost some of its internal nodes
  357. due to deletion or it could have once been balanced but now be growing
  358. by insertion without yet undergoing rebalancing.</para>
  359.  
  360. </section>
  361.  
  362. <section>
  363. <title>Three principle considerations in tree design</title>
  364.  
  365. <itemizedlist>
  366. <title>Three of the principle considerations in tree design are:</title>
  367. <listitem>
  368. <para>the <emphasis>fanout </emphasis> rate (see below)</para>
  369. </listitem>
  370. <listitem>
  371. <para>the tightness of packing</para>
  372. </listitem>
  373. <listitem>
  374. <para>the amount of the shifting of items in the tree from one node to
  375. another that is performed (which creates delays due to waiting while
  376. things move around in RAM, and on disk).</para>
  377. </listitem>
  378. </itemizedlist>
  379. </section>
  380.  
  381. <section>
  382. <title>Fanout</title>
  383. <para>
  384. The fanout rate <emphasis role="strong">n</emphasis> refers to how
  385. many nodes may be pointed to by each level's nodes. (see Figure 2-1)
  386. If each node can point to
  387. <emphasis role="strong">n</emphasis> nodes of the level below it, then
  388. starting from the top, the root node points to n internal nodes at the
  389. next level, each of which points to n more internal nodes at its next
  390. level, and so on... <emphasis role="strong">m</emphasis> levels of
  391. internal nodes can point to <emphasis role="strong">n</emphasis> in
  392. power of <emphasis role="strong">m</emphasis> leaf nodes containing
  393. items in the last level.  The more you want to be able to store in the
  394. tree, the larger you have to the fields in the key that first
  395. distinguish the objects (the <emphasis> objectids </emphasis>), and
  396. then select parts of the object (the
  397. <emphasis>offsets</emphasis>).  This means your keys must be larger,
  398. which decreases fanout (unless you compress your keys, but that will
  399. wait for our next version....).</para>
  400.   
  401. <para>
  402. <inlinemediaobject>
  403. <imageobject>
  404. <imagedata fileref="../treepics/treepicswin/Fanouts.gif"
  405. format="GIF"/>
  406. </imageobject>
  407. <textobject>
  408. <para>A four level tree with fanout N = 1 is shown. It has
  409. just four nodes starting from the root node, traversing the internal
  410. and twig nodes and ending with the leaf node which contains the
  411. data. Then there is a graph with N = 2; that is it starts with a root
  412. node, traverses 2 internal nodes, each of which points to two twig
  413. nodes (for a total of four twig nodes) and each of these twig nodes
  414. points to 2 leaf nodes for a total of 8 leaf nodes in the four
  415. levels. Lastly, a fanout N = 3 tree is shown which has 1 root node, 3
  416. internal nodes, 9 twig nodes, and 27 leaf nodes.
  417. </para></textobject>
  418. </inlinemediaobject>
  419. </para>
  420. <para> Figure 2-1.  Three 4 level, height balanced trees with
  421. <emphasis role="strong">fanouts</emphasis> n = 1, 2, and 3.  The first
  422. graph is a four level tree with fanout n = 1. It has just four nodes,
  423. starts with the (red) root node, traverses the (burgundy) internal and
  424. (blue) twig nodes, and ends with the (green) leaf node which contains
  425. the data. The second tree, with 4 levels and fanout n = 2, starts with
  426. a root node, traverses 2 internal nodes, each of which points to two
  427. twig nodes (for a total of four twig nodes), and each of these points
  428. to 2 leaf nodes for a total of 8 leaf nodes. Lastly, a 4 level, fanout
  429. n = 3 tree is shown which has 1 root node, 3 internal nodes, 9 twig
  430. nodes, and 27 leaf nodes.</para>
  431. </section>
  432.  
  433. <section>
  434. <title>What Are B+Trees, and Why Are They Better than B-Trees</title>
  435.  
  436. <para>It is possible to store not just pointers and keys in internal
  437. nodes, but also to store the objects those keys correspond to in the
  438. internal nodes.  This is what the original B-tree algorithms did.</para>
  439.  
  440. <para>Then B+trees were invented in which only pointers and keys are
  441. stored in internal nodes, and all of the objects are stored at the
  442. leaf level.</para>
  443.  
  444. <para>
  445. <inlinemediaobject>
  446. <imageobject>
  447. <imagedata fileref="../treepics/treepicswin/B-tree.gif" format="GIF"/>
  448. </imageobject>
  449. </inlinemediaobject>
  450. </para>
  451.  
  452. <para>
  453. <inlinemediaobject>
  454. <imageobject>
  455. <imagedata fileref="../treepics/treepicswin/B-plus-tree.gif" format="GIF"/>
  456. </imageobject>
  457. </inlinemediaobject>
  458. </para>
  459.  
  460. <para>
  461. Warning! I found from experience that most persons who don't first
  462. deeply understand why B+trees are better than B-Trees won't later
  463. understand explanations of the advantages of putting extents on the
  464. twig level rather than using BLOBs.  The same principles that make
  465. B+Trees better than B-Trees, also make Reiser4 faster than using BLOBs
  466. like most databases do.  So make sure this section fully digests
  467. before moving on to the next section, ok?;-) 
  468. </para>
  469. </section>
  470.  
  471. <section>
  472. <title>B+Trees Have Higher Fanout Than B-Trees</title>
  473.  
  474. <para>Fanout is increased when we put only pointers and keys in internal
  475. nodes, and don't dilute them with object data.  Increased fanout
  476. increases our ability to cache all of the internal nodes because there
  477. are fewer internal nodes.</para>
  478.  
  479. <para>Often persons respond to this by saying, "but B-trees cache
  480. objects, and caching objects is just as valuable".  It is not, on
  481. average, is the answer.  Of course, discussing averages makes the
  482. discussion much harder.  </para>
  483.  
  484. <para>We need to discuss some cache design principles for a while before
  485. we can get to this.</para>
  486. </section>
  487.  
  488. <section>
  489. <title>Cache Design Principles</title>
  490.  
  491. <section>
  492. <title>Reiser's Untie The Uncorrelated Principle of Cache Design</title>
  493. <para>
  494. <emphasis>Tying the caching of things whose usage does not
  495. strongly correlate is bad.</emphasis>
  496. </para>
  497.  
  498. <para>Suppose:</para>
  499.  
  500. <itemizedlist>
  501.  
  502. <listitem>
  503. <para>you have two sets of things, A and B.</para></listitem>
  504.  
  505. <listitem>
  506. <para>you need things from those two sets at semi-random, with there
  507. existing a tendency for some items to be needed much more frequently
  508. than others, but which items those are can shift slowly over
  509. time.</para>
  510. </listitem>
  511.  
  512. <listitem>
  513. <para>you can keep things around after you use them in a cache of
  514. limited size.</para>
  515. </listitem>
  516.  
  517. <listitem>
  518. <para>
  519. you tie the caching of every thing from A to the caching of another
  520. thing from B.  (that means, whenever you fetch something from A into
  521. the cache, you fetch its partner from B into the
  522. cache)</para>
  523. </listitem>
  524.  
  525. </itemizedlist>
  526.  
  527. <para>
  528. Then this increases the amount of cache required to store everything
  529. recently accessed from A.  If there is a strong correlation between
  530. the need for the two particular objects that are tied in each of the
  531. pairings, stronger than the gain from spending those cache resources
  532. on caching more members of B according to the LRU algorithm, then this
  533. might be worthwhile.  If there is no such strong correlation, then it
  534. is bad.</para>
  535.  
  536. <para>
  537. But wait, you might say, you need things from B also, so it is good
  538. that some of them were cached.  Yes, you need some random subset of B.
  539. The problem is that without a correlation existing, the things from B
  540. that you need are not especially likely to be those same things from B
  541. that were tied to the things from A that were needed.</para>
  542.  
  543. <para>
  544. This tendency to inefficiently tie things that are randomly needed
  545. exists outside the computer industry.  For instance, suppose you like
  546. both popcorn and sushi, with your need for them on a particular day
  547. being random.  Suppose that you like movies randomly.  Suppose a
  548. theater requires you to eat only popcorn while watching the movie you
  549. randomly found optimal to watch, and not eat sushi from the restaurant
  550. on the corner while watching that movie.  Is this a socially optimum
  551. system?  Suppose quality is randomly distributed across all the hot
  552. dog vendors: if you can only eat the hot dog produced by the best
  553. movie displayer on a particular night that you want to watch a movie,
  554. and you aren't allowed to bring in hot dogs from outside the movie
  555. theater, is it a socially optimum system?  Optimal for you?</para>
  556.  
  557. <para>
  558. Tying the uncorrelated is a very common error in designing caches, but
  559. it is still not enough to describe why B+Trees are better.  With
  560. internal nodes, we store more than one pointer per node.  That means
  561. that pointers are not separately cached.  You could well argue that
  562. pointers and the objects they point to are more strongly correlated
  563. than the different pointers.  We need another cache design
  564. principle.</para>
  565. </section>
  566.  
  567. <section>
  568. <title>Reiser's Maximize The Variance Principle of Cache Design</title> 
  569.  
  570. <para><emphasis>If two types of things that are cached and accessed, in units that
  571. are aggregates, have different average temperatures, then segregating
  572. the two types into separate units helps caching.
  573. </emphasis></para>
  574.  
  575. <para>For balanced trees, these units of aggregates are nodes.  This
  576. principle applies to the situation where it may be necessary to tie
  577. things into larger units for efficient access, and guides what things
  578. should be tied together.</para>
  579.  
  580. <para> Suppose you have R bytes of RAM for cache, and D bytes of disk.
  581. Suppose that 80% of accesses are to the most recently used things
  582. which are stored in H (hotset) bytes of nodes.  Reducing the size of H
  583. to where it is smaller than R is very important to performance. If you
  584. evenly disperse your frequently accessed data, then a larger cache is
  585. required and caching is less effective.</para>
  586.  
  587. <orderedlist>
  588. <listitem>
  589. <para>If, all else being equal, we increase the variation
  590. in temperature among all aggregates (nodes), then we increase the effectiveness of
  591. using a fast small cache.</para>
  592. </listitem>
  593.  
  594. <listitem>
  595. <para>If two types of things have different average
  596. <emphasis>temperatures </emphasis> (ratios of likelihood of access to size in
  597. bytes), then separating them into separate aggregates (nodes) increases the
  598. variation in temperature in the system as a whole.
  599. </para></listitem>
  600.  
  601. <listitem>
  602. <para>Conclusion: If all else is equal, if two types of things
  603. cached several to an aggregate (node) have different average
  604. temperatures then segregating them into separate nodes helps
  605. caching.</para>
  606. </listitem>
  607.  
  608. </orderedlist>
  609.  
  610. </section></section>
  611.  
  612. <section>
  613. <title>Pointers To Nodes Have A Higher Average Temperature Than The Nodes They Point To</title>
  614.  
  615. <para>Pointers to nodes tend to be frequently accessed relative to the
  616. number of bytes required to cache them.  Consider that you have to use
  617. the pointers for all tree traversals that reach the nodes beneath them
  618. and they are smaller than the nodes they point to.</para>
  619.  
  620. <para> Putting only node pointers and delimiting keys into internal
  621. nodes concentrates the pointers. Since pointers tend to be more
  622. frequently accessed per byte of their size than items storing file
  623. bodies, a high average temperature difference exists between pointers
  624. and object data. </para>
  625.  
  626. <para> According to the caching principles described above,
  627. segregating these two types of things with different average
  628. temperatures, pointers and object data, increases the efficiency of
  629. caching.</para>
  630. </section>
  631.  
  632. <section>
  633. <title>Segregating By Temperature Directly</title>
  634.  
  635. <para>Now you might say, well, why not segregate by actual temperature
  636. instead of by type which only correlates with temperature?  We do what
  637. we can easily and effectively code, with not just temperature
  638. segregation in consideration. There are tree designs which rearrange
  639. the tree so that objects which have a higher temperature are higher in
  640. the tree than pointers with a lower temperature.  The difference in
  641. average temperature between object data and pointers to nodes is so
  642. high that I don't find such designs a compelling optimization, and
  643. they add complexity.  I could be wrong.</para>
  644.  
  645. <para>If one had no compelling semantic basis for aggregating objects
  646. near each other (this is true for some applications), and if one
  647. wanted to access objects by nodes rather than individually, it would
  648. be interesting to have a node repacker sort object data into nodes by
  649. temperature.  You would need to have the repacker change the keys of
  650. the objects it sorts.  Perhaps someone will have us implement that for
  651. some application someday for Reiser4.</para>
  652. </section>
  653.  
  654. <section>
  655. <title>BLOBs Unbalance the Tree, Reduce Segregation of Pointers and Data,
  656. and Thereby Reduce Performance</title>
  657.  
  658. <para><emphasis>BLOBs</emphasis>, Binary Large OBjects, are a method
  659. of storing objects larger than a node by storing pointers to nodes
  660. containing the object. These pointers are commonly stored in what is
  661. called the leaf nodes (level 1, except that the BLOBs are then sort of
  662. a basement "level B" :-\ ) of a "B*" tree.</para>
  663.  
  664. <para>  
  665. <inlinemediaobject>
  666. <imageobject>
  667. <imagedata fileref="../treepics/treepicswin/Blobs_Reiser3.gif"/>
  668. </imageobject>
  669. <textobject>
  670. <para>This is a tree that was four levels until a BLOB was
  671. inserted with a pointer from a leaf node. In this case the BLOB's
  672. blocks are all contiguous.</para>
  673. </textobject>
  674. </inlinemediaobject>
  675. </para>
  676.  
  677. <para>Fig. 1.12. A Binary Large OBject (<emphasis
  678. role="strong">BLOB</emphasis>) has been inserted with, in a leaf node,
  679. pointers to its blocks. This is what a ReiserFS V3 tree looks
  680. like. </para>
  681.   
  682. <para>
  683. BLOBs are a significant unintentional definitional drift, albeit one
  684. accepted by the entire database community.  This placement of pointers
  685. into nodes containing data is a performance problem for ReiserFS V3
  686. which uses BLOBs (Never accept that "let's just try it my way and see
  687. and we can change it if it doesn't work" argument.  It took years and
  688. a disk format change to get BLOBs out of ReiserFS, and performance
  689. suffered the whole time (if tails were turned on.)).  Because the
  690. pointers to BLOBs are diluted by data, it makes caching all pointers
  691. to all nodes in RAM infeasible for typical file sets.</para>
  692.  
  693. <para> 
  694. Reiser4 returns to the classical definition of a height balanced tree
  695. in which the lengths of the paths to all leaf nodes are equal.  It does not
  696. try to pretend that all of the nodes storing objects larger than a node are
  697. somehow not part of the tree even though the tree stores pointers to them.
  698.  As a result, the amount of RAM required to store pointers to nodes is dramatically
  699. reduced. For typical configurations, RAM is large enough to hold all of the
  700. internal nodes.</para>
  701.  
  702. <para> 
  703. <inlinemediaobject>
  704. <imageobject>
  705. <imagedata fileref="../treepics/treepicswin/Blobs_Reiser4.gif"
  706. format="GIF"/>
  707. </imageobject>
  708. <textobject>
  709. <para>This is a Reiser4 tree with a BLOB in the level 1 Leaf Nodes and
  710. the pointer to it in the level 3 Twig Nodes. In this case the BLOB's
  711. blocks are all contiguous.</para>
  712. </textobject>
  713. </inlinemediaobject>
  714. </para>
  715.  
  716. <para>
  717. Fig. 1.13. A <emphasis role="strong">Reiser4, 4 level, height balanced
  718.  tree</emphasis> with <emphasis role="strong">fanout</emphasis> = 3
  719.  and the<emphasis role="strong"> BLOB</emphasis> (extent) stored in
  720.  the level 1 leaf nodes. (For reasons of space, it is set below the
  721.  other leaf nodes, but its extent-pointer is in a level 2 twig node
  722.  like every other item's pointer. </para>
  723.  
  724. <!-- Don't set it below the other nodes, space is not a good enough reason. -->
  725. <!-- <para> 
  726. Remember Reiser4 stores extents at the "twig" level which is one level above the leaf nodes. See
  727. Figure 6.</para>
  728.     
  729. <para class="center">
  730. <img src="../treepics/figXbl0bv4-6_1x1020x410.jpg"
  731.  alt="This is a tree that was four levels until a BLOB  was inserted with a pointer from a leaf node. In this case the BLOB's blocks are all contiguous."/></p>
  732.  
  733.  
  734. <para class="figure">
  735. Fig. 1.14. XXXXXXXXXXXXX </para>  -->
  736.  
  737. <para> 
  738. Gray and Reuter say the criterion for searching external memory is
  739. to "minimize the number of different pages along the average (or longest)
  740. search path.  ....by reducing the number of different pages for an arbitrary
  741. search path, the probability of having to read a block from disk is reduced."
  742. (1993, Transaction Processing: concepts and techniques, Morgan Kaufman 
  743. Publishers, San Francisco, CA, p.834 ...)</para>
  744.   
  745. <para>
  746. My problem with this explanation of why the height balanced approach
  747. is effective is that it does not convey that you can get away with
  748. having a moderately unbalanced tree provided that you do not
  749. significantly increase the total number of internal nodes.  In
  750. practice, most trees that are unbalanced do have significantly more
  751. internal nodes.  In practice, most moderately unbalanced trees have a
  752. moderate increase in the cost of in-memory tree traversals, and an
  753. immoderate increase in the amount of IO due to the increased number of
  754. internal nodes.  But if one were to put all the BLOBs together in the
  755. same location in the tree, since the amount of internal nodes would
  756. not significantly increase, the performance penalty for having them on
  757. a lower level of the tree than all other leaf nodes would not be a
  758. significant additional IO cost.  There would be a moderate increase in
  759. that part of the tree traversal time cost which is dependent on RAM
  760. speed, but this would not be so critical.  Segregating BLOBs could
  761. perhaps substantially recover the performance lost by architects not
  762. noticing the drift in the definition of height balancing for trees.
  763. It might be undesirable to segregate objects by their size rather than
  764. just their semantics though. Perhaps someday someone will try it and
  765. see what results. </para>
  766. </section></section></section>
  767.  
  768.  
  769. <section><title>Block Allocation</title>
  770. <section><title>Parent First Pre-Order Is What We Try For</title>
  771. <section><title>Version 3 Too Readily Adds Disk Seeks Between Nodes In Response To Changes In The Tree</title>
  772.  
  773. <para>In ReiserFS we attempt to assign keys for what we store in the tree
  774. such that typical accesses are accesses to objects in the order of
  775. their keys. Since one generally has to access a parent before
  776. accessing its children, this means accesses will be in parent first
  777. pre-order, as we draw it below (the below is an unbalanced tree).</para>
  778.  
  779. <para>
  780. <inlinemediaobject>
  781. <imageobject> 
  782. <imagedata fileref="parent-first-traversal.gif" format="GIF"/>
  783. </imageobject>
  784. </inlinemediaobject>
  785. </para>
  786.  
  787. <para>In the block allocation code we then attempt to assign block
  788. numbers such that accesses to nodes in parent first preorder involve
  789. accessing nodes in the order of their block numbers. </para>
  790.  
  791. <para>This added layer of abstraction in which we control layout by
  792. key assignment policy, and assign blocknumbers by tree order, is very
  793. flexible in practice, and we find it improves substantially our
  794. ability to perform more experiments with layout alternatives, and to
  795. adapt for particular application needs. </para>
  796.  
  797. <para>In Version 3, whenever we insert a node into the tree, we assign it
  798. a block number at the time we insert it. We assign it a block number
  799. as close as we can to its left neighbor in the tree. </para>
  800.  
  801. <para>One problem is that the tree can be very different in its structure
  802. at the time we insert a given block compared to when we finish some
  803. batch operation such as copying a directory and all of its files, or
  804. even just copying the current file if it is a large file. The less
  805. fanout there is, the more that shifts of items tend to lead to
  806. structural changes in the tree that change what the optimal block
  807. allocation is. The less accurate we are in allocating blocks, the more
  808. seeks. </para>
  809.  
  810. <para>In Version 4, we adopt new techniques to reduce this problem: we
  811. increase fanout by getting rid of BLOBs as described at
  812. www.namesys.com/v4/v4.html, and we allocate block numbers at flush
  813. time (when dirty nodes are written to disk) instead of at node
  814. insertion time.  </para>
  815.  
  816. <para>We determined, as part of the art of design compromise, that
  817. placing twigs before their children sadly matters enough to
  818. performance to merit doing it, but placing branches before their
  819. children does not because branches are more likely to be cached and
  820. are relatively rare.  Branches are therefor write optimized rather
  821. than read-optimized in their layout --- that means we write the
  822. branches at the end of flushing an atom, and we try to write them all
  823. together in a location likely to not require a seek away from what was
  824. written just before them.  In respect to allocating blocks for twigs,
  825. I say sadly, because the added complexity of allocating blocks for
  826. twigs just before their children is immense when you consider that we
  827. are also squeezing them, and squeezing and encrypting and compressing
  828. their children, as we allocate blocks, and the effect of the squeezing
  829. and allocating and encrypting can affect the number of extents
  830. required to store a file which affects how much fits into a twig which
  831. affects who is a child of what twig....  We did it, but not easily, I
  832. regret to say.  If a future designer finds that he can neglect
  833. read-optimizing twigs, perhaps as a result of a design improvement or
  834. a different usage pattern, it will immensely simplify the code, and
  835. this point is worth noting.  </para>
  836.  
  837. </section></section>
  838. <section><title>Dancing Trees</title>
  839.  
  840. <para> Traditional balanced tree algorithms apply a fixed criterion
  841. for whether a node is tightly enough packed, or should be combined
  842. with its neighbors.  For example, some will check on each modification
  843. whether the node modified plus its left and its right neighbor can be
  844. squeezed into one fewer nodes.  ReiserFS V3 does this on the leaf
  845. level of the tree.  The stricter this algorithm is, and the more
  846. neighbors it considers, the tighter the packing is, and the more bytes
  847. must be shifted on average per modification in order to make space for
  848. that modification.  That means more overhead in both CPU and
  849. IO.</para>
  850.  
  851. <para>
  852. For this reason, some databases actually employ free on empty as their
  853. static space usage balancing criterion.</para>
  854.  
  855. <para> "Garbage collection" algorithms are a different way of managing
  856. space usage in memory.  When space runs short, they run through memory
  857. repacking it and reclaiming unused space, rather than repacking with
  858. each usage or freeing of memory as they might otherwise have done.
  859. This is like not driving to the recycler every time you finish a
  860. bottle of milk.  (ReiserFS is environment friendly --- we recycle
  861. memory rather than discarding it.... )</para>
  862.  
  863. <para> With Reiser4, when we are short on space in RAM, the memory
  864. manager asks us to write a dirty node to disk.  We collect together
  865. the maximal set of nodes that include the node we were asked to flush,
  866. are contiguous together with each other, and are dirty.  We call this set a
  867. <emphasis>slum</emphasis>. We squeeze the slum into the minimum number
  868. of nodes it can squeeze into. If the slum is, say, 64 nodes large
  869. after squeezing, that will be much tighter than a fixed balancing
  870. criterion tree would pack it because it will be at least 63/64ths
  871. full, and most tree balancing algorithms examine only the immediate
  872. neighbors when deciding how tight they can pack. (If the slum is one
  873. node in size, we have several options we are experimenting with,
  874. including the one we suspect will be best for "typical" use, which is
  875. leaving it for the repacker to optimize well.) </para>
  876.  
  877. <para>The effect of this approach is that hot spots become relatively
  878. loosely packed so that insertions are cheap, and slums that are no
  879. longer hot spots get very tightly packed before being sent to
  880. disk. </para>
  881.  
  882. <para>An area for possible future work is to allow for slum squeezing to
  883. eliminate the need for performing IO if enough memory is freed by it.
  884. This might require changes to the kernel memory manager though.</para>
  885.  
  886. <para> This resembles traditional garbage collection algorithms. So far
  887. as we know though, applying this approach to trees is unique to
  888. reiser4, and we call a tree whose packing is managed in this way a
  889. "dancing tree" rather than a "space usage balanced tree". (Remember,
  890. there is a difference between height balancing and space usage
  891. balancing, and Reiser4 trees are still (very) height balanced
  892. trees....) </para>
  893.  
  894. <section>
  895. <title>Repacker</title>
  896.  
  897. <para>Another way of escaping from the balancing time vs. space
  898. efficiency tradeoff is to use a repacker.  80% of files on the disk
  899. remain unchanged for long periods of time.  It is efficient to pack
  900. them perfectly, by using a repacker that runs much less often than
  901. every write to disk.  This repacker goes through the entire tree
  902. ordering, from left to right and then from right to left, alternating
  903. each time it runs.  When it goes from left to right in the tree
  904. ordering, it shoves everything as far to the left as it will go, and
  905. when it goes from right to left it shoves everything as far to the
  906. right as it will go.  (Left means small in key or in block number:-)
  907. ).  In the absence of FS activity the effect of this over time is to
  908. sort by tree order (defragment), and to pack with perfect
  909. efficiency.</para>
  910.  
  911. <para>
  912. Reiser4.1 will modify the repacker to insert controlled "air holes", as
  913. it is well known that insertion efficiency is harmed by overly tight
  914. packing.</para>
  915.  
  916. <para>
  917. I hypothesize that it is more efficient to periodically run a repacker
  918. that systematically repacks using large IOs than to perform lots of 1
  919. block reads of neighboring nodes of the modification points so as to
  920. preserve a balancing invariant in the face of poorly localized
  921. modifications to the tree.</para>
  922.  
  923. <section>
  924. <title>What Is Best Done Just Before Flushing To Disk vs. At Tree
  925. Insertion Time vs. In Nightly Repacker</title>
  926.  
  927. <para>Painting it in broad strokes, Reiser4 is designed to massage and
  928. performance optimize changes to data in three stages: </para>
  929.  
  930. <itemizedlist>
  931. <listitem>
  932. <para>at the time the system call that invokes Reiser4 executes (this
  933. may put the data into the tree, the page cache, the dentry cache,
  934. etc.)</para>
  935. </listitem>
  936. <listitem>
  937. <para>at the time the data is flushed from a cache (usually to
  938. disk)</para>
  939. </listitem>
  940. <listitem>
  941. <para>at the time the repacker runs </para>
  942. </listitem>
  943. </itemizedlist>
  944.  
  945. <para>Different kinds of information massaging belong in each of these stages: </para>
  946.  
  947. <itemizedlist>
  948. <listitem>
  949. <para>Object plugins modify the data in ways appropriate to do as the
  950. data is being changed.</para>
  951. </listitem>
  952. <listitem>
  953. <para>Flush plugins modify the data in ways appropriate to do as the
  954. data is flushed to disk.</para>
  955. </listitem>
  956. <listitem>
  957. <para>The repacker modifies the data in ways appropriate to do only at long time intervals. </para>
  958. </listitem>
  959. </itemizedlist>
  960.  
  961. <para>I see the flushing stage as the time most optimal for: </para>
  962.  
  963. <itemizedlist>
  964. <listitem>
  965. <para>allocating blocks to nodes, including deciding whether to
  966. overwrite or relocate</para> 
  967. </listitem>
  968. <listitem>
  969. <para>squeezing nodes together for tight packing before they go to disk</para>
  970. </listitem>
  971. <listitem>
  972. <para>encrypting data</para>
  973. </listitem>
  974. </itemizedlist>
  975.  
  976. </section>
  977. </section>
  978. <section><title>A Hint of Things Left For Other Papers</title> <para>At
  979. the time of writing we implement all VFS operations atomically, and
  980. the infrastructure for atomic operations is fully complete.  The
  981. interface for specifying multiple FS operations that are atomic is
  982. still being tested and debugged, we are going to let our mailing list comment
  983. before we fix it in stone, and space in this paper is limited, so we
  984. have left it for a future, hopefully interesting, paper.</para>
  985.  
  986. <para>
  987. Expediency often tempts security researchers into adding
  988. architecturally inelegant extensions to filesystems.  Reiser4's
  989. objective was to systematically reviewed all of the infrastructure
  990. lackings that tempt security researchers into implementing inelegance,
  991. and systematically approached remedying them.  It creates a complete
  992. plugin infrastructure that allows you to construct custom made file
  993. objects.  It implements inheritance, constraints, encryption at flush
  994. time, and compression at flush time.  We implement all of the features
  995. needed to effectively implement security attributes as just flavors of
  996. files.  Space does not permit discussing these features, or the
  997. philosophy behind why security attributes should be just particular
  998. flavors of files, in this paper, but please watch our website at
  999. www.namesys.com.  </para>
  1000.  
  1001. </section>
  1002. <section><title>Conclusion</title>
  1003.  
  1004. <para>Atomic filesystems keep your data more secure.  Historically
  1005. filesystems have not been made atomic in large part because of the
  1006. performance cost.  One component of this performance cost is the need
  1007. to write twice, and Reiser4 avoids this.  Reiser4 introduces a set of
  1008. performance improvements that dwarf what additional cost of atomicity
  1009. remains after the write twice penalty is eliminated, and the result is
  1010. that you are going to have both greater data security and faster
  1011. performance when you use it.  Application writers are going to be able
  1012. to avoid a variety of security holes by tapping into the atomic
  1013. operation functionality.</para>
  1014.  
  1015. </section>
  1016. <section><title>References</title>
  1017.  
  1018. <para>A summary of some influences upon Reiser4 performance design: </para>
  1019.  
  1020. <itemizedlist>
  1021. <listitem>
  1022. <para>Its avoidance of seeks for writes is influenced by LFS and WAFL.</para>
  1023. </listitem>
  1024. <listitem>
  1025. <para>Plans for a repacker in Reiser4.1 resemble the use of a cleaner in LFS.</para>
  1026. </listitem>
  1027. <listitem>
  1028. <para>Its flush plugins are a generalization of delayed block
  1029. allocation used in XFS.</para>
  1030. </listitem>
  1031. <listitem>
  1032. <para>Performance problems, benefits (larger blocks), and space
  1033. savings, due to tails in ReiserFS V3 (not Reiser4) resemble
  1034. performance problems (these are not well documented in the literature
  1035. unfortunately), benefits (they do document in the FFS paper cited
  1036. below the benefit of being able to increase block size as a result of
  1037. not fearing as much its cost in space usage), and space savings, due
  1038. to fragments in FFS.</para> 
  1039. </listitem>
  1040. <listitem>
  1041. <para>The use of balanced trees for aggregating multiple small
  1042. objects into one node resembles how they are used in
  1043. databases.</para>
  1044. </listitem>
  1045. </itemizedlist>
  1046. </section></section></section>
  1047.  
  1048. <section><title>Citations:</title>
  1049.  
  1050. <itemizedlist>
  1051.  
  1052. <listitem>
  1053. <para><emphasis>[Gray93]</emphasis></para>
  1054. <para>Jim Gray and Andreas Reuter. "Transaction Processing: Concepts and Techniques". Morgan Kaufmann Publishers, Inc., 1993. Old but good textbook on transactions.
  1055. Available at <ulink url="http://www.mkp.com/books_catalog/catalog.asp?ISBN=1-55860-190-2">
  1056. http://www.mkp.com/books_catalog/catalog.asp?ISBN=1-55860-190-2</ulink></para>
  1057. </listitem>
  1058.  
  1059. <listitem>
  1060. <para><emphasis>[Hitz94]</emphasis></para>
  1061. <para>D. Hitz, J. Lau and M. Malcolm. "File system design for an NFS file server appliance". Proceedings of the 1994 USENIX Winter Technical Conference, pp. 235-246, San Francisco, CA, January 1994
  1062. Available at <ulink url="http://citeseer.nj.nec.com/hitz95file.html">http://citeseer.nj.nec.com/hitz95file.html</ulink> </para>
  1063. </listitem>
  1064.  
  1065. <listitem>
  1066. <para><emphasis>[TR3001]</emphasis></para>
  1067. <para>D. Hitz. "A Storage Networking Appliance". Tech. Rep TR3001, Network Appliance, Inc., 1995
  1068. Available at <ulink url="http://www.netapp.com/tech_library/3001.html">http://www.netapp.com/tech_library/3001.html</ulink> </para>
  1069. </listitem>
  1070.  
  1071. <listitem>
  1072. <para><emphasis>[TR3002]</emphasis></para>
  1073. <para>D. Hitz, J. Lau and M. Malcolm. "File system design for an NFS file server appliance". Tech. Rep. TR3002, Network Appliance, Inc., 1995
  1074. Available at <ulink url="http://www.netapp.com/tech_library/3002.html">http://www.netapp.com/tech_library/3002.html</ulink> </para>
  1075. </listitem>
  1076.  
  1077. <listitem>
  1078. <para><emphasis>[Kusick84]</emphasis></para>
  1079. <para>M. McKusick, W. Joy, S. Leffler, R. Fabry. "A Fast File System for UNIX". ACM Transactions on Computer Systems, Vol. 2, No. 3, pp. 181-197, August 1984
  1080. Available at <ulink url="http://citeseer.nj.nec.com/mckusick84fast.html">http://citeseer.nj.nec.com/mckusick84fast.html</ulink> </para>
  1081. </listitem>
  1082.  
  1083. <listitem>
  1084. <para><emphasis>[Ousterh89]</emphasis></para>
  1085. <para>J. Ousterhout and F. Douglis. "Beating the I/O Bottleneck: A Case for Log-Structured File Systems". ACM Operating System Reviews, Vol. 23, No. 1, pp.11-28, January 1989
  1086. Available at <ulink url="http://citeseer.nj.nec.com/ousterhout88beating.html">http://citeseer.nj.nec.com/ousterhout88beating.html</ulink> </para>
  1087. </listitem>
  1088.  
  1089. <listitem>
  1090. <para><emphasis>[Seltzer95]</emphasis></para>
  1091. <para>M. Seltzer, K. Smith, H. Balakrishnan, J. Chang, S. McMains and V. Padmanabhan. "File System Logging versus Clustering: A Performance Comparison". Proceedings of the 1995 USENIX Technical Conference, pp. 249-264, New Orleans, LA, January 1995 
  1092. Available at <ulink url="http://citeseer.nj.nec.com/seltzer95file.html">http://citeseer.nj.nec.com/seltzer95file.html</ulink> </para>
  1093. </listitem>
  1094.  
  1095. <listitem>
  1096. <para><emphasis>[Seltzer95Supp]</emphasis></para>
  1097. <para>M. Seltzer. "LFS and FFS Supplementary Information". 1995
  1098. <ulink url="http://www.eecs.harvard.edu/~margo/usenix.195/">http://www.eecs.harvard.edu/~margo/usenix.195/</ulink> </para>
  1099. </listitem>
  1100.  
  1101. <listitem>
  1102. <para><emphasis>[Ousterh93Crit]</emphasis></para>
  1103. <para>J. Ousterhout. "A Critique of Seltzer's 1993 USENIX Paper"
  1104. <ulink url="http://www.eecs.harvard.edu/~margo/usenix.195/ouster_critique1.html">http://www.eecs.harvard.edu/~margo/usenix.195/ouster_critique1.html</ulink> </para>
  1105. </listitem>
  1106.  
  1107. <listitem>
  1108. <para><emphasis>[Ousterh95Crit]</emphasis></para>
  1109. <para>J. Ousterhout. "A Critique of Seltzer's LFS Measurements"
  1110. <ulink url="http://www.eecs.harvard.edu/~margo/usenix.195/ouster_critique2.html">http://www.eecs.harvard.edu/~margo/usenix.195/ouster_critique2.html</ulink> </para>
  1111. </listitem>
  1112.  
  1113. <listitem>
  1114. <para><emphasis>[Rosenblum92]</emphasis></para>
  1115. <para>M. Rosenblum and J. Ousterhout. "The design and implementation of a log-structured file system". ACM Transactions on Computer Systems, Vol. 10, No. 1, pp. 26-52, February 1992
  1116. Available at <ulink url="http://citeseer.nj.nec.com/rosenblum91design.html">http://citeseer.nj.nec.com/rosenblum91design.html</ulink> </para>
  1117. </listitem>
  1118.  
  1119. <listitem>
  1120. <para><emphasis>[SwD96]</emphasis></para>
  1121. <para>A. Sweeny, D. Doucette, W. Hu, C. Anderson, M. Nishimoto and G. Peck. "Scalability in the XFS File System". Proceedings of the 1996 USENIX Technical Conference, pp. 1-14, San Diego, CA, January 1996
  1122. Available at <ulink url="http://citeseer.nj.nec.com/sweeney96scalability.html">http://citeseer.nj.nec.com/sweeney96scalability.html</ulink> </para>
  1123. </listitem>
  1124. </itemizedlist>
  1125. </section>
  1126. </article>
  1127.  
  1128.  
  1129.  
  1130.