001: /*
002:
003: Derby - Class org.apache.derby.impl.store.access.btree.BTreePostCommit
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.store.access.btree;
023:
024: import org.apache.derby.iapi.services.context.ContextManager;
025: import org.apache.derby.iapi.services.daemon.Serviceable;
026: import org.apache.derby.iapi.services.monitor.Monitor;
027: import org.apache.derby.iapi.services.sanity.SanityManager;
028: import org.apache.derby.iapi.error.StandardException;
029:
030: import org.apache.derby.iapi.store.access.AccessFactory;
031: import org.apache.derby.iapi.store.access.AccessFactoryGlobals;
032: import org.apache.derby.iapi.store.access.ConglomerateController;
033: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
034: import org.apache.derby.iapi.store.access.TransactionController;
035: import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo;
036: import org.apache.derby.iapi.store.access.conglomerate.TransactionManager;
037:
038: import org.apache.derby.iapi.store.access.Qualifier;
039:
040: import org.apache.derby.iapi.store.raw.ContainerHandle;
041: import org.apache.derby.iapi.store.raw.FetchDescriptor;
042: import org.apache.derby.iapi.store.raw.LockingPolicy;
043: import org.apache.derby.iapi.store.raw.Page;
044: import org.apache.derby.iapi.store.raw.RecordHandle;
045: import org.apache.derby.iapi.store.raw.Transaction;
046:
047: import org.apache.derby.iapi.types.DataValueDescriptor;
048:
049: import org.apache.derby.iapi.services.io.FormatableBitSet;
050: import org.apache.derby.iapi.reference.SQLState;
051:
052: /**
053:
054: The BTreePostCommit class implements the Serviceable protocol.
055:
056: In it's role as a Serviceable object, it stores the state necessary to
057: find a page in a btree that may have committed delete's to reclaim.
058:
059: In it's role as a PostCommitProcessor it looks up the page described, and
060: reclaims space in the btree. It first trys to clean up any deleted commits
061: on the page. It then will shrink the tree if it is going to delete all
062: rows from the page (RESOLVE - not done yet).
063:
064: **/
065:
066: class BTreePostCommit implements Serviceable {
067: private AccessFactory access_factory = null;
068: private long page_number = ContainerHandle.INVALID_PAGE_NUMBER;
069:
070: protected BTree btree = null;
071:
072: /* Constructors for This class: */
073: BTreePostCommit(AccessFactory access_factory, BTree btree,
074: long input_page_number) {
075: this .access_factory = access_factory;
076: this .btree = btree;
077: this .page_number = input_page_number;
078: }
079:
080: /* Private/Protected methods of This class: */
081:
082: /* Public Methods of This class: */
083:
084: /* Public Methods of Serviceable class: */
085:
086: /**
087: * The urgency of this post commit work.
088: * <p>
089: * This determines where this Serviceable is put in the post commit
090: * queue. Post commit work in the btree can be safely delayed until there
091: * is not user work to do.
092: *
093: * @return false, this work should not be serviced ASAP
094: **/
095: public boolean serviceASAP() {
096: return (true);
097: }
098:
099: // @return true, if this work needs to be done on a user thread immediately
100: public boolean serviceImmediately() {
101: return false;
102: }
103:
104: private final void doShrink(OpenBTree open_btree,
105: DataValueDescriptor[] shrink_row) throws StandardException {
106: ControlRow root = null;
107:
108: /*
109: System.out.println(
110: "Calling shrink on tree with levels = " +
111: open_btree.getHeight() + "\n");
112: */
113:
114: // Get the root page back, and perform a split following the
115: // to-be-inserted key. The split releases the root page latch.
116: root = ControlRow.Get(open_btree, BTree.ROOTPAGEID);
117:
118: root.shrinkFor(open_btree, shrink_row);
119:
120: root = null;
121:
122: // on return from shrinkFor the root pointer is invalid. The
123: // latch has been released, the root page may have changed from
124: // a branch to a leaf.
125:
126: return;
127: }
128:
129: /**
130: * perform the work described in the postcommit work.
131: * <p>
132: * In this implementation the only work that can be executed by this
133: * post commit processor is this class itself.
134: * <p>
135: *
136: * @return Returns Serviceable.DONE when work has completed, or
137: * returns Serviceable.REQUEUE if work needs to be requeued.
138: *
139: * @param contextMgr the context manager started by the
140: * post commit daemon
141: *
142: * @exception StandardException Standard exception policy.
143: **/
144: public int performWork(ContextManager contextMgr)
145: throws StandardException {
146:
147: // requeue if work was not completed in this try because of locks
148: boolean requeue_work = false;
149:
150: TransactionManager tc = (TransactionManager) this .access_factory
151: .getAndNameTransaction(contextMgr,
152: AccessFactoryGlobals.SYS_TRANS_NAME);
153:
154: TransactionManager internal_xact = tc.getInternalTransaction();
155:
156: if (SanityManager.DEBUG) {
157: if (SanityManager.DEBUG_ON("verbose_btree_post_commit"))
158: System.out.println("starting internal xact\n");
159: }
160:
161: OpenBTree open_btree = new OpenBTree();
162:
163: try {
164: // Get lock on base table.
165:
166: // The current space reclamation algorithm requires a table level
167: // lock on the btree - this is mostly because the shrink algorithm
168: // is not multi-user. This lock is requested NOWAIT as it does
169: // not want to impedede normal operation on the table. If the lock
170: // were to wait then the current lock manager livelock algorithm
171: // would block all subsequent lock requests on this btree even if
172: // they are compatible with the current holder of the lock.
173: //
174: // There are currently 3 outstanding enhancement requests:
175: // track 4237 - retry the work intelligently
176: // track 4238 - if can't get table lock, at least reclaim the rows
177: // track 4239 - do row level lock shrink - very hard to do.
178: //
179: ConglomerateController base_cc = btree
180: .lockTable(
181: internal_xact,
182: (ContainerHandle.MODE_FORUPDATE | ContainerHandle.MODE_LOCK_NOWAIT),
183: TransactionController.MODE_TABLE,
184: TransactionController.ISOLATION_REPEATABLE_READ);
185:
186: open_btree
187: .init(
188: (TransactionManager) null,
189: internal_xact,
190: (ContainerHandle) null, // open the container
191: internal_xact.getRawStoreXact(),
192: false,
193: ContainerHandle.MODE_FORUPDATE,
194: TransactionController.MODE_TABLE,
195: btree
196: .getBtreeLockingPolicy(
197: internal_xact
198: .getRawStoreXact(),
199: TransactionController.MODE_TABLE,
200: LockingPolicy.MODE_CONTAINER,
201: TransactionController.ISOLATION_REPEATABLE_READ,
202: base_cc, open_btree),
203: btree, (LogicalUndo) null, // No logical undo necessry.
204: (DynamicCompiledOpenConglomInfo) null);
205:
206: DataValueDescriptor[] shrink_key = purgeCommittedDeletes(
207: open_btree, this .page_number);
208:
209: // RESOLVE (mikem) - move this call when doing row level locking.
210: if (shrink_key != null)
211: doShrink(open_btree, shrink_key);
212:
213: open_btree.close();
214: } catch (StandardException se) {
215:
216: //2 kinds of errors here expected here. Either container not found or dead lock.
217: // It is possible by the time this post commit work gets scheduled
218: // that the container has been dropped and that the open container
219: // call will return null - in this case just return assuming no
220: // work to be done.
221:
222: //If it is a locking error, work is requeued. (4237)
223:
224: if (se.getMessageId().equals(SQLState.LOCK_TIMEOUT)
225: || se.getMessageId().equals(SQLState.DEADLOCK)) {
226: requeue_work = true;
227: }
228:
229: //RESSOLVE-mike (4238) If you can't get a table level lock for btree space recovery in
230: //the post commit thread, maybe you should at least reclaim the
231: //rows on the page while you are at it. Use the same algorithm
232: //as exists in BTreeController.java. row level shrink is still a
233: //big problem and a separate track exists for it.
234:
235: } finally {
236: internal_xact.commit();
237: internal_xact.destroy();
238: }
239:
240: return (requeue_work ? Serviceable.REQUEUE : Serviceable.DONE);
241: }
242:
243: private final DataValueDescriptor[] getShrinkKey(
244: OpenBTree open_btree, ControlRow control_row, int slot_no)
245: throws StandardException {
246: DataValueDescriptor[] shrink_key = open_btree.getConglomerate()
247: .createTemplate();
248:
249: control_row.page.fetchFromSlot((RecordHandle) null, slot_no,
250: shrink_key, (FetchDescriptor) null, true);
251:
252: return (shrink_key);
253: }
254:
255: /**
256: * Reclaim space taken up by committed deleted rows.
257: * <p>
258: * This routine assumes it has been called by an internal transaction which
259: * has performed no work so far, and that it has an exclusive table lock.
260: * These assumptions mean that any deleted rows encountered must be from
261: * committed transactions (otherwise we could not have gotten the exclusive
262: * table lock).
263: * <p>
264: * RESOLVE (mikem) - under row locking this routine must do more work to
265: * determine a deleted row is a committed deleted row.
266: *
267: * @param open_btree The btree already opened.
268: * @param pageno The page number of the page to look for committed deletes.
269: *
270: * @exception StandardException Standard exception policy.
271: **/
272: private final DataValueDescriptor[] purgeCommittedDeletes(
273: OpenBTree open_btree, long pageno) throws StandardException {
274: ControlRow control_row = null;
275: DataValueDescriptor[] shrink_key = null;
276:
277: try {
278: // The following can fail either if it can't get the latch or
279: // somehow the page requested no longer exists. In either case
280: // the post commit work will just skip it.
281: control_row = ControlRow.GetNoWait(open_btree, pageno);
282:
283: if (control_row != null) {
284: Page page = control_row.page;
285:
286: // The number records that can be reclaimed is:
287: // total recs - control row - recs_not_deleted
288: int num_possible_commit_delete = page.recordCount() - 1
289: - page.nonDeletedRecordCount();
290:
291: if (num_possible_commit_delete > 0) {
292: // loop backward so that purges which affect the slot table
293: // don't affect the loop (ie. they only move records we
294: // have already looked at).
295: for (int slot_no = page.recordCount() - 1; slot_no > 0; slot_no--) {
296: if (page.isDeletedAtSlot(slot_no)) {
297:
298: if (page.recordCount() == 2) {
299: // About to purge last row from page so
300: // remember the key so we can shrink the
301: // tree.
302: shrink_key = this .getShrinkKey(
303: open_btree, control_row,
304: slot_no);
305: }
306:
307: page.purgeAtSlot(slot_no, 1, true);
308:
309: if (SanityManager.DEBUG) {
310: if (SanityManager
311: .DEBUG_ON("verbose_btree_post_commit")) {
312: System.out.println("Purging row["
313: + slot_no + "]"
314: + "on page:" + pageno
315: + ".\n");
316: }
317: }
318: }
319: }
320: }
321:
322: if (page.recordCount() == 1) {
323: if (SanityManager.DEBUG) {
324: if (SanityManager
325: .DEBUG_ON("verbose_btree_post_commit")) {
326: System.out.println("Chance to shrink.\n");
327: }
328: }
329: }
330: } else {
331: if (SanityManager.DEBUG) {
332: if (SanityManager
333: .DEBUG_ON("verbose_btree_post_commit")) {
334: System.out
335: .println("Get No Wait returned null. page num = "
336: + pageno + "\n");
337: }
338: }
339: }
340: } finally {
341: if (control_row != null)
342: control_row.release();
343: }
344:
345: return (shrink_key);
346: }
347:
348: }
|