Design.fileop
上传用户:tsgydb
上传日期:2007-04-14
资源大小:10674k
文件大小:17k
源码类别:

MySQL数据库

开发平台:

Visual C++

  1. # $Id: Design.fileop,v 11.4 2000/02/19 20:57:54 bostic Exp $
  2. The design of file operation recovery.
  3. Keith has asked me to write up notes on our current status of database
  4. create and delete and recovery, why it's so hard, and how we've violated
  5. all the cornerstone assumptions on which our recovery framework is based.
  6. I am including two documents at the end of this one.   The first is the
  7. initial design of the recoverability of file create and delete (there is
  8. no talk of subdatabases there, because we didn't think we'd have to do
  9. anything special there).  I will annotate this document on where things
  10. changed.
  11. The second is the design of recd007 which is supposed to test our ability
  12. to recover these operations regardless of where one crashes.  This test
  13. is fundamentally different from our other recovery tests in the following
  14. manner.  Normally, the application controls transaction boundaries.
  15. Therefore, we can perform an operation and then decide whether to commit
  16. or abort it.  In the normal recovery tests, we force the database into
  17. each of the four possible states from a recovery perspective:
  18. database is pre-op, undo (do nothing)
  19. database is pre-op, redo
  20. database is post-op, undo
  21. database is post-op, redo (do nothing)
  22. By copying databases at various points and initiating txn_commit and abort
  23. appropriately, we can make all these things happen.  Notice that the one
  24. case we don't handle is where page A is in one state (e.g., pre-op) and
  25. page B is in another state (e.g., post-op).  I will argue that these don't
  26. matter because each page is recovered independently.  If anyone can poke
  27. holes in this, I'm interested.
  28. The problem with create/delete recovery testing is that the transaction
  29. is begun and ended all inside the library.  Therefore, there is never any
  30. point (outside the library) where we can copy files and or initiate
  31. abort/commit.  In order to still put the recovery code through its paces,
  32. Sue designed an infrastructure that lets you tell the library where to
  33. make copies of things and where to suddenly inject errors so that the
  34. transaction gets aborted.  This level of detail allows us to push the
  35. create/delete recovery code through just about every recovery path
  36. possible (although I'm sure Mike will tell me I'm wrong when he starts to
  37. run code coverage tools).
  38. OK, so that's all preamble and a brief discussion of the documents I'm
  39. enclosing.
  40. Why was this so hard and painful and why is the code so Q@#$!% complicated?
  41. The following is a discussion/explanation, but to the best of my knowledge,
  42. the structure we have in place now works.  The key question we need to be
  43. asking is, "Does this need to have to be so complex or should we redesign
  44. portions to simplify it?"  At this point, there is no obvious way to simplify
  45. it in my book, but I may be having difficulty seeing this because my mind is
  46. too polluted at this point.
  47. Our overall strategy for recovery is that we do write-ahead logging,
  48. that is we log an operation and make sure it is on disk before any
  49. data corresponding to the data that log record describes is on disk.
  50. Typically we use log sequence numbers (LSNs) to mark the data so that
  51. during recovery, we can look at the data and determine if it is in a
  52. state before a particular log record or after a particular log record.
  53. In the good old days, opens were not transaction protected, so we could
  54. do regular old opens during recovery and if the file existed, we opened
  55. it and if it didn't (or appeared corrupt), we didn't and treated it like
  56. a missing file.  As will be discussed below in detail, our states are much
  57. more complicated and recovery can't make such simplistic assumptions.
  58. Also, since we are now dealing with file system operations, we have less
  59. control about when they actually happen and what the state of the system
  60. can be.  That is, we have to write create log records synchronously, because
  61. the create/open system call may force a newly created (0-length) file to
  62. disk.  This file has to now be identified as being in the "being-created"
  63. state.
  64. A. We used to make a number of assumptions during recovery:
  65. 1. We could call db_open at any time and one of three things would happen:
  66. a) the file would be opened cleanly
  67. b) the file would not exist
  68. c) we would encounter an error while opening the file
  69. Case a posed no difficulty.
  70. In Case b, we simply spit out a warning that a file was missing and then
  71. ignored all subsequent operations to that file.
  72. In Case c, we reported a fatal error.
  73. 2.  We can always generate a warning if a file is missing.
  74. 3. We never encounter NULL file names in the log.
  75. B. We also made some assumptions in the main-line library:
  76. 1. If you try to open a file and it exists but is 0-length, then
  77. someone else is trying to open it.
  78. 2. You can write pages anywhere in a file and any non-existent pages
  79. are 0-filled. [This breaks on Windows.]
  80. 3. If you have proper permissions then you can always evict pages from
  81. the buffer pool.
  82. 4. During open, we can close the master database handle as soon as
  83. we're done with it since all the rest of the activity will take place
  84. on the subdatabase handle.
  85. In our brave new world, most of these assumptions are no longer valid.
  86. Let's address them one at a time.
  87. A.1 We could call db_open at any time and one of three things would happen:
  88. a) the file would be opened cleanly
  89. b) the file would not exist
  90. c) we would encounter an error while opening the file
  91. There are now additional states.  Since we are trying to make file
  92. operations recoverable, you can now die in the middle of such an
  93. operation and we have to be able to pick up the pieces.  What this
  94. now means is that:
  95. * a 0-length file can be an indication of a create in-progress
  96. * you can have a meta-data page but no root page (of a btree)
  97. * if a file doesn't exist, it could mean that it was just about
  98. to be created and needs to be rolled forward.
  99. * if you encounter an error in a file (e.g., the meta-data page
  100. is all 0's) you could still be in mid-open.
  101. I have now made this all work, but it required significant changes to the
  102. db_open code and error handling and this is the sort of change that makes
  103. everyone nervous.
  104. A.2.  We can always generate a warning if a file is missing.
  105. Now that we have a delete file method in the API, we need to make sure
  106. that we do not generate warning messages for files that don't exist if
  107. we see that they were explicitly deleted.
  108. This means that we need to save state during recovery, determine which
  109. files were missing and were not being recreated and were not deleted and
  110. only complain about those.
  111. A.3. We never encounter NULL file names in the log.
  112. Now that we allow tranaction protection on memory-resident files, we write
  113. log messages for files with NULL file names.  This means that our assumption
  114. of always being able to call "db_open" on any log_register OPEN message found
  115. in the log is no longer valid.
  116. B.1. If you try to open a file and it exists but is 0-length, then
  117. someone else is trying to open it.
  118. As discussed for A.1, this is no longer true.  It may be instead that you
  119. are in the process of recovering a create.
  120. B.2. You can write pages anywhere in a file and any non-existent pages
  121. are 0-filled.
  122. It turns out that this is not true on Windows.  This means that places
  123. we do group allocation (hash) must explicitly allocate each page, because
  124. we can't count on recognizing the uninitialized pages later.
  125. B.3. If you have proper permissions then you can always evict pages from
  126. the buffer pool.
  127. In the brave new world though, files can be deleted and they may
  128. have pages in the mpool.  If you later try to evict these, you
  129. discover that the file doesn't exist.  We'd get here when we had
  130. to dirty pages during a remove operation.
  131. B.4. You can close files any time you want.
  132. However, if the file takes part in the open/remove transaction,
  133. then we had better not close it until after the transaction
  134. commits/aborts, because we need to be able to get our hands on the
  135. dbp and the open happened in a different transaction.
  136. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  137. Design for recovering file create and delete in the presence of subdatabases.
  138. Assumptions:
  139. Remove the O_TRUNCATE flag.
  140. Single-thread all open/create/delete operations.
  141. (Well, almost all; we'll optimize opens without DB_CREATE set.)
  142. The reasoning for this is that with two simultaneous
  143. open/creaters, during recovery, we cannot identify which
  144. transaction successfully created files and therefore cannot
  145. recovery correctly.
  146. File system creates/deletes are synchronous
  147. Once the file is open, subdatabase creates look like regular
  148. get/put operations and a metadata page creation.
  149. There are 4 cases to deal with:
  150. 1. Open/create file
  151. 2. Open/create subdatabase
  152. 3. Delete
  153. 4. Recovery records
  154. __db_fileopen_recover
  155. __db_metapage_recover
  156. __db_delete_recover
  157. existing c_put and c_get routines for subdatabase creation
  158. Note that the open/create of the file and the open/create of the
  159. subdatabase need to be in the same transaction.
  160. 1. Open/create (full file and subdb version)
  161. If create
  162. LOCK_FILEOP
  163. txn_begin
  164. log create message (open message below)
  165. do file system open/create
  166. if we did not create
  167. abort transaction (before going to open_only)
  168. if (!subdb)
  169. set dbp->open_txn = NULL
  170. else
  171. txn_begin a new transaction for the subdb open
  172. construct meta-data page
  173. log meta-data page (see metapage)
  174. write the meta-data page
  175. * It may be the case that btrees need to log both meta-data pages
  176.   and root pages. If that is the case, I believe that we can use
  177.   this same record and recovery routines for both
  178. txn_commit
  179. UNLOCK_FILEOP
  180. 2. Delete
  181. LOCK_FILEOP
  182. txn_begin
  183. log delete message (delete message below)
  184. mv file __db.file.lsn
  185. txn_commit
  186. unlink __db.file.lsn
  187. UNLOCK_FILEOP
  188. 3. Recovery Routines
  189. __db_fileopen_recover
  190. if (argp->name.size == 0
  191. done;
  192. if (redo) /* Commit */
  193. __os_open(argp->name, DB_OSO_CREATE, argp->mode, &fh)
  194. __os_closehandle(fh)
  195. if (undo) /* Abort */
  196. if (argp->name exists)
  197. unlink(argp->name);
  198. __db_metapage_recover
  199. if (redo)
  200. __os_open(argp->name, 0, 0, &fh)
  201. __os_lseek(meta data page)
  202. __os_write(meta data page)
  203. __os_closehandle(fh);
  204. if (undo)
  205. done = 0;
  206. if (argp->name exists)
  207. if (length of argp->name != 0)
  208. __os_open(argp->name, 0, 0, &fh)
  209. __os_lseek(meta data page)
  210. __os_read(meta data page)
  211. if (read succeeds && page lsn != current_lsn)
  212. done = 1
  213. __os_closehandle(fh);
  214. if (!done)
  215. unlink(argp->name)
  216. __db_delete_recover
  217. if (redo)
  218. Check if the backup file still exists and if so, delete it.
  219. if (undo)
  220. if (__db_appname(__db.file.lsn exists))
  221. mv __db_appname(__db.file.lsn) __db_appname(file)
  222. __db_metasub_recover
  223. /* This is like a normal recovery routine */
  224. Get the metadata page
  225. if (cmp_n && redo)
  226. copy the log page onto the page
  227. update the lsn
  228. make sure page gets put dirty
  229. else if (cmp_p && undo)
  230. update the lsn to the lsn in the log record
  231. make sure page gets put dirty
  232. if the page was modified, put it back dirty
  233. In db.src
  234. # name: filename (before call to __db_appname)
  235. # mode: file system mode
  236. BEGIN open
  237. DBT name DBT s
  238. ARG mode u_int32_t o
  239. END
  240. # opcode: indicate if it is a create/delete and if it is a subdatabase
  241. # pgsize: page size on which we're going to write the meta-data page
  242. # pgno: page number on which to write this meta-data page
  243. # page: the actual meta-data page
  244. # lsn: LSN of the meta-data page -- 0 for new databases, may be non-0
  245. # for subdatabases.
  246. BEGIN metapage
  247. ARG opcode u_int32_t x
  248. DBT name DBT s
  249. ARG pgno db_pgno_t d
  250. DBT page DBT s
  251. POINTER lsn DB_LSN * lu
  252. END
  253. # We do not need a subdatabase name here because removing a subdatabase
  254. # name is simply a regular bt_delete operation from the master database.
  255. # It will get logged normally.
  256. # name: filename
  257. BEGIN delete
  258. DBT name DBT s
  259. END
  260. # We also need to reclaim pages, but we can use the existing
  261. # bt_pg_alloc routines.
  262. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  263. Testing recoverability of create/delete.
  264. These tests are unlike other tests in that they are going to
  265. require hooks in the library.  The reason is that the create
  266. and delete calls are internally wrapped in a transaction, so
  267. that if the call returns, the transaction has already either
  268. commited or aborted.  Using only that interface limits what
  269. kind of testing we can do.  To match our other recovery testing
  270. efforts, we need to add hooks to trigger aborts at particular
  271. times in the create/delete path.
  272. The general recovery testing strategy is that we wish to
  273. execute every path through every recovery routine.  That
  274. means that we try to:
  275. catch each operation in its pre-operation state
  276. call the recovery function with redo
  277. call the recovery function with undo
  278. catch each operation in its post-operation state
  279. call the recovery function with redo
  280. call the recovery function with undo
  281. In addition, there are a few critical points in the create and
  282. delete path that we want to make sure we capture.
  283. 1. Test Structure
  284. The test structure should be similar to the existing recovery
  285. tests.  We will want to have a structure in place where we
  286. can execute different commands:
  287. create a file/database
  288. create a file that will contain subdatabases.
  289. create a subdatabase
  290. remove a subdatabase (that contains valid data)
  291. remove a subdatabase (that does not contain any data)
  292. remove a file that used to contain subdatabases
  293. remove a file that contains a database
  294. The tricky part is capturing the state of the world at the
  295. various points in the create/delete process.
  296. The critical points in the create process are:
  297. 1. After we've logged the create, but before we've done anything.
  298. in db/db.c
  299. after the open_retry
  300. after the __crdel_fileopen_log call (and before we've
  301. called __os_open).
  302. 2. Immediately after the __os_open
  303. 3. Immediately after each __db_log_page call
  304. in bt_open.c
  305. log meta-data page
  306. log root page
  307. in hash.c
  308. log meta-data page
  309. 4. With respect to the log records above, shortly after each
  310. log write is an memp_fput.  We need to do a sync after
  311. each memp_fput and trigger a point after that sync.
  312. The critical points in the remove process are:
  313. 1. Right after the crdel_delete_log in db/db.c
  314. 2. Right after the __os_rename call (below the crdel_delete_log)
  315. 3. After the __db_remove_callback call.
  316. I believe that there are the places where we'll need some sort of hook.
  317. 2. Adding hooks to the library.
  318. The hooks need two components.  One component is to capture the state of
  319. the database at the hook point and the other is to trigger a txn_abort at
  320. the hook point.  The second part is fairly trivial.
  321. The first part requires more thought.  Let me explain what we do in a
  322. "normal" recovery test.  In a normal recovery test, we save an intial
  323. copy of the database (this copy is called init).  Then we execute one
  324. or more operations.  Then, right before the commit/abort, we sync the
  325. file, and save another copy (the afterop copy).  Finally, we call txn_commit
  326. or txn_abort, sync the file again, and save the database one last time (the
  327. final copy).
  328. Then we run recovery.  The first time, this should be a no-op, because
  329. we've either committed the transaction and are checking to redo it or
  330. we aborted the transaction, undid it on the abort and are checking to
  331. undo it again.
  332. We then run recovery again on whatever database will force us through
  333. the path that requires work.  In the commit case, this means we start
  334. with the init copy of the database and run recovery.  This pushes us
  335. through all the redo paths.  In the abort case, we start with the afterop
  336. copy which pushes us through all the undo cases.
  337. In some sense, we're asking the create/delete test to be more exhaustive
  338. by defining all the trigger points, but I think that's the correct thing
  339. to do, since the create/delete is not initiated by a user transaction.
  340. So, what do we have to do at the hook points?
  341. 1. sync the file to disk.
  342. 2. save the file itself
  343. 3. save any files named __db_backup_name(name, &backup_name, lsn)
  344. Since we may not know the right lsns, I think we should save
  345. every file of the form __db.name.0xNNNNNNNN.0xNNNNNNNN into
  346. some temporary files from which we can restore it to run
  347. recovery.
  348. 3. Putting it all together
  349. So, the three pieces are writing the test structure, putting in the hooks
  350. and then writing the recovery portions so that we restore the right thing
  351. that the hooks saved in order to initiate recovery.
  352. Some of the technical issues that need to be solved are:
  353. How does the hook code become active (i.e., we don't
  354. want it in there normally, but it's got to be
  355. there when you configure for testing)?
  356. How do you (the test) tell the library that you want a
  357. particular hook to abort?
  358. How do you (the test) tell the library that you want the
  359. hook code doing its copies (do we really want
  360. *every* test doing these copies during testing?
  361. Maybe it's not a big deal, but maybe it is; we
  362. should at least think about it).