001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: INFileReader.java,v 1.52.2.5 2008/01/07 15:14:13 cwl Exp $
007: */
008:
009: package com.sleepycat.je.log;
010:
011: import java.io.IOException;
012: import java.nio.ByteBuffer;
013: import java.util.HashMap;
014: import java.util.Map;
015:
016: import com.sleepycat.je.DatabaseException;
017: import com.sleepycat.je.cleaner.TrackedFileSummary;
018: import com.sleepycat.je.cleaner.UtilizationTracker;
019: import com.sleepycat.je.dbi.DatabaseId;
020: import com.sleepycat.je.dbi.DbTree;
021: import com.sleepycat.je.dbi.EnvironmentImpl;
022: import com.sleepycat.je.log.entry.INContainingEntry;
023: import com.sleepycat.je.log.entry.INLogEntry;
024: import com.sleepycat.je.log.entry.LNLogEntry;
025: import com.sleepycat.je.log.entry.LogEntry;
026: import com.sleepycat.je.log.entry.NodeLogEntry;
027: import com.sleepycat.je.tree.FileSummaryLN;
028: import com.sleepycat.je.tree.IN;
029: import com.sleepycat.je.tree.INDeleteInfo;
030: import com.sleepycat.je.tree.INDupDeleteInfo;
031: import com.sleepycat.je.tree.MapLN;
032: import com.sleepycat.je.utilint.DbLsn;
033:
034: /**
035: * INFileReader supports recovery by scanning log files during the IN rebuild
036: * pass. It looks for internal nodes (all types), segregated by whether they
037: * belong to the main tree or the duplicate trees.
038: *
039: * <p>This file reader can also be run in tracking mode to keep track of the
040: * maximum node id, database id and txn id seen so those sequences can be
041: * updated properly at recovery. In this mode it also performs utilization
042: * counting. It is only run once in tracking mode per recovery, in the
043: * first phase of recovery.</p>
044: */
045: public class INFileReader extends FileReader {
046:
047: /* Information about the last entry seen. */
048: private boolean lastEntryWasDelete;
049: private boolean lastEntryWasDupDelete;
050: private LogEntryType fromLogType;
051: private boolean isProvisional;
052:
053: /*
054: * targetEntryMap maps DbLogEntryTypes to log entries. We use this
055: * collection to find the right LogEntry instance to read in the
056: * current entry.
057: */
058: private Map targetEntryMap;
059: private LogEntry targetLogEntry;
060:
061: /*
062: * For tracking non-target log entries.
063: * Note that dbIdTrackingEntry and txnIdTrackingEntry do not overlap with
064: * targetLogEntry, since the former are LNs and the latter are INs.
065: * But nodeTrackingEntry and inTrackingEntry can overlap with the others,
066: * and we only load one of them when they do overlap.
067: */
068: private Map dbIdTrackingMap;
069: private LNLogEntry dbIdTrackingEntry;
070: private Map txnIdTrackingMap;
071: private LNLogEntry txnIdTrackingEntry;
072: private Map otherNodeTrackingMap;
073: private NodeLogEntry nodeTrackingEntry;
074: private INLogEntry inTrackingEntry;
075: private LNLogEntry fsTrackingEntry;
076:
077: /*
078: * If trackIds is true, peruse all node entries for the maximum
079: * node id, check all MapLNs for the maximum db id, and check all
080: * LNs for the maximum txn id
081: */
082: private boolean trackIds;
083: private long maxNodeId;
084: private int maxDbId;
085: private long maxTxnId;
086: private boolean mapDbOnly;
087:
088: /* Used for utilization tracking. */
089: private long partialCkptStart;
090: private UtilizationTracker tracker;
091: private Map fileSummaryLsns;
092:
093: /**
094: * Create this reader to start at a given LSN.
095: */
096: public INFileReader(EnvironmentImpl env, int readBufferSize,
097: long startLsn, long finishLsn, boolean trackIds,
098: boolean mapDbOnly, long partialCkptStart,
099: Map fileSummaryLsns) throws IOException, DatabaseException {
100:
101: super (env, readBufferSize, true, startLsn, null,
102: DbLsn.NULL_LSN, finishLsn);
103:
104: this .trackIds = trackIds;
105: this .mapDbOnly = mapDbOnly;
106: targetEntryMap = new HashMap();
107:
108: if (trackIds) {
109: maxNodeId = 0;
110: maxDbId = 0;
111: tracker = env.getUtilizationTracker();
112: this .partialCkptStart = partialCkptStart;
113: this .fileSummaryLsns = fileSummaryLsns;
114: fsTrackingEntry = (LNLogEntry) LogEntryType.LOG_FILESUMMARYLN
115: .getNewLogEntry();
116:
117: dbIdTrackingMap = new HashMap();
118: txnIdTrackingMap = new HashMap();
119: otherNodeTrackingMap = new HashMap();
120:
121: dbIdTrackingMap.put(LogEntryType.LOG_MAPLN_TRANSACTIONAL,
122: LogEntryType.LOG_MAPLN_TRANSACTIONAL
123: .getNewLogEntry());
124: dbIdTrackingMap.put(LogEntryType.LOG_MAPLN,
125: LogEntryType.LOG_MAPLN.getNewLogEntry());
126: txnIdTrackingMap.put(LogEntryType.LOG_LN_TRANSACTIONAL,
127: LogEntryType.LOG_LN_TRANSACTIONAL.getNewLogEntry());
128: txnIdTrackingMap.put(LogEntryType.LOG_MAPLN_TRANSACTIONAL,
129: LogEntryType.LOG_MAPLN_TRANSACTIONAL
130: .getNewLogEntry());
131: txnIdTrackingMap.put(LogEntryType.LOG_NAMELN_TRANSACTIONAL,
132: LogEntryType.LOG_NAMELN_TRANSACTIONAL
133: .getNewLogEntry());
134: txnIdTrackingMap.put(
135: LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL,
136: LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL
137: .getNewLogEntry());
138: txnIdTrackingMap.put(
139: LogEntryType.LOG_DUPCOUNTLN_TRANSACTIONAL,
140: LogEntryType.LOG_DUPCOUNTLN_TRANSACTIONAL
141: .getNewLogEntry());
142: }
143: }
144:
145: /**
146: * Configure this reader to target this kind of entry.
147: */
148: public void addTargetType(LogEntryType entryType)
149: throws DatabaseException {
150:
151: targetEntryMap.put(entryType, entryType.getNewLogEntry());
152: }
153:
154: /**
155: * If we're tracking node, database and txn ids, we want to see all node
156: * log entries. If not, we only want to see IN entries.
157: * @return true if this is an IN entry.
158: */
159: protected boolean isTargetEntry(byte entryTypeNum,
160: byte entryTypeVersion) throws DatabaseException {
161:
162: lastEntryWasDelete = false;
163: lastEntryWasDupDelete = false;
164: targetLogEntry = null;
165: dbIdTrackingEntry = null;
166: txnIdTrackingEntry = null;
167: nodeTrackingEntry = null;
168: inTrackingEntry = null;
169: fsTrackingEntry = null;
170: isProvisional = LogEntryType
171: .isEntryProvisional(entryTypeVersion);
172:
173: /* Get the log entry type instance we need to read the entry. */
174: fromLogType = LogEntryType.findType(entryTypeNum,
175: entryTypeVersion);
176: LogEntry possibleTarget = (LogEntry) targetEntryMap
177: .get(fromLogType);
178:
179: /*
180: * If the entry is provisional, we won't be reading it in its entirety;
181: * otherwise, we try to establish targetLogEntry.
182: */
183: if (!isProvisional) {
184: targetLogEntry = possibleTarget;
185: }
186:
187: /* Was the log entry an IN deletion? */
188: if (LogEntryType.LOG_IN_DELETE_INFO.equals(fromLogType)) {
189: lastEntryWasDelete = true;
190: }
191:
192: if (LogEntryType.LOG_IN_DUPDELETE_INFO.equals(fromLogType)) {
193: lastEntryWasDupDelete = true;
194: }
195:
196: if (trackIds) {
197:
198: /*
199: * Check if it's a db or txn id tracking entry. Note that these
200: * entries do not overlap with targetLogEntry.
201: */
202: if (!isProvisional) {
203: dbIdTrackingEntry = (LNLogEntry) dbIdTrackingMap
204: .get(fromLogType);
205: txnIdTrackingEntry = (LNLogEntry) txnIdTrackingMap
206: .get(fromLogType);
207: }
208:
209: /*
210: * Determine nodeTrackingEntry, inTrackingEntry, fsTrackingEntry.
211: * Note that these entries do overlap with targetLogEntry.
212: */
213: if (fromLogType.isNodeType()) {
214: if (possibleTarget != null) {
215: nodeTrackingEntry = (NodeLogEntry) possibleTarget;
216: } else if (dbIdTrackingEntry != null) {
217: nodeTrackingEntry = dbIdTrackingEntry;
218: } else if (txnIdTrackingEntry != null) {
219: nodeTrackingEntry = txnIdTrackingEntry;
220: } else {
221: nodeTrackingEntry = (NodeLogEntry) otherNodeTrackingMap
222: .get(fromLogType);
223: if (nodeTrackingEntry == null) {
224: nodeTrackingEntry = (NodeLogEntry) fromLogType
225: .getNewLogEntry();
226: otherNodeTrackingMap.put(fromLogType,
227: nodeTrackingEntry);
228: }
229: }
230: if (nodeTrackingEntry instanceof INLogEntry) {
231: inTrackingEntry = (INLogEntry) nodeTrackingEntry;
232: }
233: if (LogEntryType.LOG_FILESUMMARYLN.equals(fromLogType)) {
234: fsTrackingEntry = (LNLogEntry) nodeTrackingEntry;
235: }
236: }
237:
238: /*
239: * Count all entries except for the file header as new.
240: * UtilizationTracker does not count the file header.
241: */
242: if (!LogEntryType.LOG_FILE_HEADER.equals(fromLogType)) {
243: tracker.countNewLogEntry(getLastLsn(), fromLogType,
244: currentEntryHeader.getSize()
245: + currentEntryHeader.getItemSize());
246: }
247:
248: /*
249: * Return true if this entry should be passed on to processEntry.
250: * If we're tracking ids, return if this is a targeted entry
251: * or if it's any kind of tracked entry or node.
252: */
253: return (targetLogEntry != null)
254: || (dbIdTrackingEntry != null)
255: || (txnIdTrackingEntry != null)
256: || (nodeTrackingEntry != null);
257: } else {
258:
259: /*
260: * Return true if this entry should be passed on to processEntry.
261: * If we're not tracking ids, only return true if it's a targeted
262: * entry.
263: */
264: return (targetLogEntry != null);
265: }
266: }
267:
268: /**
269: * This reader looks at all nodes for the max node id and database id. It
270: * only returns non-provisional INs and IN delete entries.
271: */
272: protected boolean processEntry(ByteBuffer entryBuffer)
273: throws DatabaseException {
274:
275: boolean useEntry = false;
276: boolean entryLoaded = false;
277:
278: /* If this is a targetted entry, read the entire log entry. */
279: if (targetLogEntry != null) {
280: readEntry(targetLogEntry, entryBuffer, true); // readFullItem
281: DatabaseId dbId = getDatabaseId();
282: boolean isMapDb = dbId.equals(DbTree.ID_DB_ID);
283: useEntry = (!mapDbOnly || isMapDb);
284: entryLoaded = true;
285: }
286:
287: /* Do a partial load during tracking if necessary. */
288: if (trackIds) {
289:
290: /*
291: * Do partial load of db and txn id tracking entries if necessary.
292: * Note that these entries do not overlap with targetLogEntry.
293: *
294: * We're doing a full load for now, since LNLogEntry does not read
295: * the db and txn id in a partial load, only the node id.
296: */
297: LNLogEntry lnEntry = null;
298: if (dbIdTrackingEntry != null) {
299: /* This entry has a db id */
300: lnEntry = dbIdTrackingEntry;
301: readEntry(lnEntry, entryBuffer, true); // readFullItem
302: entryLoaded = true;
303: MapLN mapLN = (MapLN) lnEntry.getMainItem();
304: int dbId = mapLN.getDatabase().getId().getId();
305: if (dbId > maxDbId) {
306: maxDbId = dbId;
307: }
308: }
309: if (txnIdTrackingEntry != null) {
310: /* This entry has a txn id */
311: if (lnEntry == null) {
312: lnEntry = txnIdTrackingEntry;
313: readEntry(lnEntry, entryBuffer, true); // readFullItem
314: entryLoaded = true;
315: }
316: long txnId = lnEntry.getTxnId().longValue();
317: if (txnId > maxTxnId) {
318: maxTxnId = txnId;
319: }
320: }
321:
322: /*
323: * Perform utilization counting under trackIds to prevent
324: * double-counting.
325: */
326: if (fsTrackingEntry != null) {
327:
328: /* Must do full load to get key from file summary LN. */
329: if (!entryLoaded) {
330: readEntry(nodeTrackingEntry, entryBuffer, true); // readFullItem
331: entryLoaded = true;
332: }
333:
334: /*
335: * When a FileSummaryLN is encountered, reset the tracked
336: * summary for that file to replay what happens when a
337: * FileSummaryLN log entry is written.
338: */
339: byte[] keyBytes = fsTrackingEntry.getKey();
340: FileSummaryLN fsln = (FileSummaryLN) fsTrackingEntry
341: .getMainItem();
342: long fileNum = fsln.getFileNumber(keyBytes);
343: TrackedFileSummary trackedLN = tracker
344: .getTrackedFile(fileNum);
345: if (trackedLN != null) {
346: trackedLN.reset();
347: }
348:
349: /* Save the LSN of the FileSummaryLN for use by undo/redo. */
350: fileSummaryLsns.put(new Long(fileNum), new Long(
351: getLastLsn()));
352:
353: /*
354: * SR 10395: Do not cache the file summary in the
355: * UtilizationProfile here, since it may be for a deleted log
356: * file.
357: */
358: }
359:
360: /*
361: * Do partial load of nodeTrackingEntry (and inTrackingEntry) if
362: * not already loaded. We only need the node id.
363: */
364: if (nodeTrackingEntry != null) {
365: if (!entryLoaded) {
366: readEntry(nodeTrackingEntry, entryBuffer, false); // readFullItem
367: entryLoaded = true;
368: }
369: /* Keep track of the largest node id seen. */
370: long nodeId = nodeTrackingEntry.getNodeId();
371: maxNodeId = (nodeId > maxNodeId) ? nodeId : maxNodeId;
372: }
373:
374: if (inTrackingEntry != null) {
375: assert entryLoaded : "All nodes should have been loaded";
376:
377: /*
378: * Count the obsolete LSN of the previous version, if available
379: * and if not already counted. Use inexact counting for two
380: * reasons: 1) we don't always have the full LSN because
381: * earlier log versions only had the file number, and 2) we
382: * can't guarantee obsoleteness for provisional INs.
383: */
384: long oldLsn = inTrackingEntry.getObsoleteLsn();
385: if (oldLsn != DbLsn.NULL_LSN) {
386: long newLsn = getLastLsn();
387: if (!isObsoleteLsnAlreadyCounted(oldLsn, newLsn)) {
388: tracker.countObsoleteNodeInexact(oldLsn,
389: fromLogType, 0);
390: }
391: }
392:
393: /*
394: * Count a provisional IN as obsolete if it follows
395: * partialCkptStart. It cannot have been already counted,
396: * because provisional INs are not normally counted as
397: * obsolete; they are only considered obsolete when they are
398: * part of a partial checkpoint.
399: *
400: * Depending on the exact point at which the checkpoint was
401: * aborted, this technique is not always accurate; therefore
402: * inexact counting must be used.
403: */
404: if (isProvisional && partialCkptStart != DbLsn.NULL_LSN) {
405: oldLsn = getLastLsn();
406: if (DbLsn.compareTo(partialCkptStart, oldLsn) < 0) {
407: tracker.countObsoleteNodeInexact(oldLsn,
408: fromLogType, 0);
409: }
410: }
411: }
412: }
413:
414: /* Return true if this entry should be processed */
415: return useEntry;
416: }
417:
418: /**
419: * Returns whether a given obsolete LSN has already been counted in the
420: * utilization profile. If true is returned, it should not be counted
421: * again, to prevent double-counting.
422: */
423: private boolean isObsoleteLsnAlreadyCounted(long oldLsn, long newLsn) {
424:
425: /* If the file summary follows the new LSN, it was already counted. */
426: Long fileNum = new Long(DbLsn.getFileNumber(oldLsn));
427: long fileSummaryLsn = DbLsn.longToLsn((Long) fileSummaryLsns
428: .get(fileNum));
429: int cmpFsLsnToNewLsn = (fileSummaryLsn != DbLsn.NULL_LSN) ? DbLsn
430: .compareTo(fileSummaryLsn, newLsn)
431: : -1;
432: return (cmpFsLsnToNewLsn >= 0);
433: }
434:
435: /**
436: * Get the last IN seen by the reader.
437: */
438: public IN getIN() throws DatabaseException {
439:
440: return ((INContainingEntry) targetLogEntry).getIN(envImpl);
441: }
442:
443: /**
444: * Get the last databaseId seen by the reader.
445: */
446: public DatabaseId getDatabaseId() {
447: if (lastEntryWasDelete) {
448: return ((INDeleteInfo) targetLogEntry.getMainItem())
449: .getDatabaseId();
450: } else if (lastEntryWasDupDelete) {
451: return ((INDupDeleteInfo) targetLogEntry.getMainItem())
452: .getDatabaseId();
453: } else {
454: return ((INContainingEntry) targetLogEntry).getDbId();
455: }
456: }
457:
458: /**
459: * Get the maximum node id seen by the reader.
460: */
461: public long getMaxNodeId() {
462: return maxNodeId;
463: }
464:
465: /**
466: * Get the maximum db id seen by the reader.
467: */
468: public int getMaxDbId() {
469: return maxDbId;
470: }
471:
472: /**
473: * Get the maximum txn id seen by the reader.
474: */
475: public long getMaxTxnId() {
476: return maxTxnId;
477: }
478:
479: /**
480: * @return true if the last entry was a delete info entry.
481: */
482: public boolean isDeleteInfo() {
483: return lastEntryWasDelete;
484: }
485:
486: /**
487: * @return true if the last entry was a dup delete info entry.
488: */
489: public boolean isDupDeleteInfo() {
490: return lastEntryWasDupDelete;
491: }
492:
493: /**
494: * Get the deleted node id stored in the last delete info log entry.
495: */
496: public long getDeletedNodeId() {
497: return ((INDeleteInfo) targetLogEntry.getMainItem())
498: .getDeletedNodeId();
499: }
500:
501: /**
502: * Get the deleted id key stored in the last delete info log entry.
503: */
504: public byte[] getDeletedIdKey() {
505: return ((INDeleteInfo) targetLogEntry.getMainItem())
506: .getDeletedIdKey();
507: }
508:
509: /**
510: * Get the deleted node id stored in the last delete info log entry.
511: */
512: public long getDupDeletedNodeId() {
513: return ((INDupDeleteInfo) targetLogEntry.getMainItem())
514: .getDeletedNodeId();
515: }
516:
517: /**
518: * Get the deleted main key stored in the last delete info log entry.
519: */
520: public byte[] getDupDeletedMainKey() {
521: return ((INDupDeleteInfo) targetLogEntry.getMainItem())
522: .getDeletedMainKey();
523: }
524:
525: /**
526: * Get the deleted main key stored in the last delete info log entry.
527: */
528: public byte[] getDupDeletedDupKey() {
529: return ((INDupDeleteInfo) targetLogEntry.getMainItem())
530: .getDeletedDupKey();
531: }
532:
533: /**
534: * Get the LSN that should represent this IN. For most INs, it's the LSN
535: * that was just read. For BINDelta entries, it's the LSN of the last
536: * full version.
537: */
538: public long getLsnOfIN() {
539: return ((INContainingEntry) targetLogEntry)
540: .getLsnOfIN(getLastLsn());
541: }
542: }
|