mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
20678: DAO5 branch: Preparation for merge back to HEAD 20689: Merged DAO4 to DAO5 - Removed all 'dbscripts/create/3.x/SomeDialect' and replaced with 'dbscripts/create/SomeDialect' DB create scripts are taken from latest DAO4 - TODO: FixAuthoritiesCrcValuesPatch needs query implementation in PatchDAO Merged DAO3 to DAO4 - Reapplied fixes for ALF-713 (race condition on Usages) 19350: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-2 to BRANCHES/DEV/V3.3-DAO-REFACTOR-3: 18939: SAIL-4 :2nd stage branch for DAO refactor off HEAD rev 18898 18948: Merged V3.3-DAO-REFACTOR to V3.3-DAO-REFACTOR-2 18202: Dev branch for DAO refactor 18252: SAIL-233: QName.hbm.xml 18295: Added missing CREATE TABLE statements for QName-related code 18324: SAIL-234: Node.hbm.xml: Node aspects initial integration 18355: Added 'setValue' method to manually update the cached value 18356: MV property stressing lowered to speed test up 18357: SAIL-234: Node.hbm.xml 18376: Pulled all Alfresco-related create SQL into script 18389: SAIL-234: Permissions DAO refactor - initial checkpoint 18390: Formatting only (line-endings) 18400: SAIL-234: Node.hbm.xml 18418: SAIL-234: Node.hbm.xml: 'alf_node_assoc' CRUD 18429: SAIL-234: Node.hbm.xml: Cleaned out all Hibernate references to NodeAssocImpl 18457: SAIL-234: Permissions DAO refactor 18959: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18479: SAIL-234: Node.hbm.xml - fix updateNode (missing id when saving oldDummyNode) 18482: SAIL-235: remove Permissions.hbm.xml 18517: SAIL-235: Permissions DAO refactor 18523: SAIL-234: Node.hbm.xml 18524: SAIL-235: Permissions DAO refactor 18960: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18533: Flipped back to Windows line endings 18535: Formatting-only (eol) 18540: Formatting-only (eol) 18541: SAIL-235: Permissions DAO refactor 18543: SAIL-234: Node.hbm.xml: Start alf_store changes 18567: SAIL-235: Permissions DAO refactor 18596: SAIL-305: Alfresco DDL - formatted/rationalized and added missing indexes & fk constraints 18603: SAIL-311: Minor cleanup for schema upgrade scripts (V3.3) 18604: SAIL-311: Remove empty dirs 18619: SAIL-274: Locale.hbm.xml 18621: Added method to create default ACL 18622: SAIL-234: Node.hbm.xml: Store, Transaction, Server and some node 18624: Formatting only (eol) 18631: SAIL-235: Permissions DAO refactor 18633: SAIL-235: Permissions DAO refactor - do not expose CRUD for AceContext (or AuthorityAlias) since currently unused 18639: getLocale(Locale) should return null if it doesn't exist 18640: SAIL-234 NodeDAO: More replacement of node queries and updates 18648: SAIL-310: Create SQL script for core repo tables (All DB ports) 18651: SAIL-234 NodeDAO: Moves across stores handle presence of target deleted nodes 18961: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18658: SAIL-274 Locale DAO: Missing getValueKey() method 18662: SAIL-235: Permissions DAO refactor - further cleanup (of DbAccessControlList usage, including copyACLs) 18664: DB scripts porting for PostgreSQL finished. 18668: SAIL-234 Node DAO: Note in case Transaction Change ID is dropped from indexes 18669: SAIL-234 Node DAO: deleteNode and archive (store move) fixes 18672: DB scripts porting for Oracle finished. 18675: SAIL-235: Permissions DAO refactor 18677: DB scripts porting for DB2 finished. 18964: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18687: Execute a callback with retries 18688: SAIL-234 Node DAO: Child association creation 18690: SAIL-234 Node DAO: Comment out raw creation of stores as it breaks subsequent bootstrap checks 18691: SAIL-234 Node DAO: More replacement of alf_child_assoc handling 18713: Commented about needing a more efficient removeChildAssociation method 18714: SAIL-234 Node DAO: Replaced queries on alf_child_assoc 18715: SAIL-234 Node DAO: More alf_child_assoc query replacement 18727: SAIL-234 Node DAO: alf_child_assoc queries complete 18737: SAIL-234 Node DAO: Tweaks to newNode and implemented prependPaths 18741: SAIL-234 and SAIL-334: Moved UsageDelta Hibernate code and queries over to UsageDeltaDAO 18748: SAIL-234 Node DAO: fix NPE (EditionServiceImplTest) 18769: SAIL-234 Node DAO: alf_node_properties ground work 18786: SAIL-234 Node DAO: alf_node_properties and cm:auditable properties 18810: Added EqualsHelper.getMapComparison 18813: TransactionalCache propagates cache clears and removals during rollback 18826: SAIL-234 Node DAO: Moved over sundry references to NodeDaoService to NodeDAO 18849: SAIL-237: UsageDelta.hbm.xml - eol formatting only (including removal of unwanted svn:eol-style=native property) 18869: SAIL-234 NodeDAO: Fixed more references to 'nodeDaoService' 18895: SAIL-234 NodeDAO: Queries for alf_transaction 18899: SAIL-234 Node DAO: Fixed bean fetching for 'nodeDAO' 18909: SAIL-234 NodeDAO: Fixes to getNodeRefStatus and various txn queries 18916: SAIL-234 NodeDAO: Fixed moveNode alf_child_assoc updates 18922: SAIL-235: DAO refactoring: Permission.hbm.xml 18930: SAIL-235: DAO refactoring: Permission.hbm.xml 18932: SAIL-234 NodeDAO: Fixing up gotchas, javadocs and some naming 18933: SAIL-234 NodeDAO: Minor neatening 18935: SAIL-234 Node DAO: Caches for ID to NodeRef and StoreRef 18936: EHCache config files line endings 18938: SAIL-237: Usage DAO refactor - initial checkpoint 18945: SAIL-235: DAO refactoring: Permission.hbm.xml. Move Node. 18975: Fix for move-node ACL jiggery-pokery 19067: SAIL-4: fix VersionHistoryImpl.getSuccessors (causing VersionServiceImplTest.testGetVersionHistorySameWorkspace failure) 19068: SAIL-234: fix VersionMigratorTest.testMigrateOneVersion 19074: SAIL-237: Usage DAO - update to common iBatis mapping pattern(s) to ease DB porting 19076: SAIL-231: Activities DAO - update to common iBatis mapping pattern(s) 19077: SAIL-232: AppliedPatch DAO - minor cleanup (comments & formatting only) 19092: Merging HEAD to DEV/V3.3-DAO-REFACTOR-2 18973: Temporarily comment out AVMTestSuite and run AVM tests individually 19056: AVM unit test improvements 19097: SAIL-235: DAO refactoring: Permission.hbm.xml: Additional index to support queries to find the id and acl id for the primary children of a node. 19185: SAIL-238: Permissions DAO - (minor) update to common iBatis mapping pattern 19289: SAIL-234 NodeDAO: Node cache replaces NodeRef cache 19302: SAIL-234 Node DAO: Added cache for node properties 19318: SAIL-4: AVM DAO - (minor) update to common iBatis mapping pattern 20690: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-4 to BRANCHES/DEV/V3.3-DAO-REFACTOR-5: 20063: (RECORD ONLY) DAO refactor branch V4 20146: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19401: SAIL-234 Node DAO: Fix permission service tests (setPrimaryChildrenSharedAclId needs to invalidate nodesCache) 19428: Fixed TransactionalCache issue with null and NullValueMarker 19429: Took empty cm:content creation out of FileFolderService#createImpl 19430: SAIL-234 Node DAO: Tweaks around caching and cm:auditable 19431: SAIL-4 DAO Refactor: Exception thrown when attempting writes in read-only txn have changed 19436: SAIL-234 Node DAO: Fix NPE during cm:auditable update 19475: Allow debugging of code without stepping into trivial stuff 19476: Follow-up on 19429 by ensuring CIFS/FTP set a mimetype on the ContentWriter 19477: SAIL-234 Node DAO: Leverage DAO better for NodeService.addProperties 19478: SAIL-234 NodeDAO: Added toString() for ParentAssocsInfo (cache value for parent assocs) 19479: SAIL-234 Node DAO: Fixed for parent association and property caches 19480: Made TransactionAwareSingleton bind-key a GUID 19481: SAIL-234 Node DAO: Reinstated 100K collection property tests 19482: SAIL-234 Node DAO: Node and property cache fixes highlighted by unit tests 19483: SAIL-234 Node DAO: Start on NodeBulkLoader implementation 19595: SAIL-234 Node DAO: Fix moveNode to detect cyclic relationship prior to updating ACLs for moved tree FileFolderServiceImplTest.testETHREEOH_3088_MoveIntoSelf) 20147: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19602: (RECORD ONLY) Reintegrated with HEAD up to rev 19433 19621: (RECORD ONLY) SAIL-347 19683: (RECORD ONLY) Reverse-merged 19621 for SAIL-347 19722: (RECORD ONLY) Merged /alfresco/HEAD:r19434-19721 20150: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19741: Merged DEV\V3.3-DAO-REFACTOR-2 to DEV\V3.3-DAO-REFACTOR-3 19739: Extended "move" tests 19743: Fix AuditableAspectTest.testAddAspect (to allow for node modified date tolerance) 19748: Remaining part of merge from HEAD to V3.3-DAO-REFACTOR-3 19367: Merged BRANCHES/V3.2 to HEAD: 19286: Fix for ALF-626 "Using 'null' as an authority argument in clearPermissions() cause a java.lang.NullPointerException" 19755: SAIL-234 Node DAO: Fix RepoAdminServiceImplTest.testConcurrentDynamicModelDelete (handle InvalidNodeRefException after getChildAssocs) 20692: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-4 to BRANCHES/DEV/V3.3-DAO-REFACTOR-5: - Retired all 1.3 and 1.4 upgrade scripts ... R.I.P. - Fixed CRC patch for Authorities (only tested on MySQL) - Fixed SQL patch revision numbers and bumped version schema number up 20158: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19773: SQL mappings and scripts: SAIL-310, SAIL-304, SAIL-303 and SAIL-347 19774: Futher fix for SAIL-310: Sequence patch must take into account sequences created for 3.3 19851: SAIL-371 (SAIL-294) NodeDAO fallout: Fix QName and Namespace read/write handling and bean name in unit test 20183: Merged DAO3 to DAO4 19852: SAIL-370: Remove LinkValidation 19853: SAIL-239 (SAIL-294) Attributes.hbm.xml: Added ability to attach arbitrary property to unique context 19857: SAIL-373 Fallout from Permissions DAO refactor (SAIL-235) 19864: SAIL-239 (SAIL-294): Removed AttributeService RMI API 19865: More SAIL-239 (SAIL-294): Removed AttributeService RMI API 20208: DAO-refactor implementation of ALF-2712 query improvements 20209: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 20060: Removal of AttributeService for SAIL-239 (SAIL-294) 20348: SAIL-371 (SAIL-294): Protect collection properties during map insert and retrieval 20547: SAIL-371 (SAIL-294) Attributes.hbm.xml: implement getAttributes + fixes 20573: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests and other fallout 20597: SAIL-239 Attributes.hbm.xml: WCM/AVM locking test fixes (wip) 20598: SAIL-239 Attributes.hbm.xml: WCM/AVM locking test fixes (wip) - fix AssetServiceImplTest.testSimpleLockFile NPE 20600: Fix PropertyValueDAOTest.testPropertyValue_Enum (follow-on to r20060 for SAIL-239 - which introduces ENUM prop vals) 20601: Fix UsageDAOTest.testCreateAndDeleteUsageDeltas NPE (would also affect ContentStoreCleanerScalabilityRunner) 20603: Fix CMISPropertyServiceTest.* (fallout from r20146 <- r19429 <- Took empty cm:content creation out of FileFolderService#createImpl) 20604: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - TransferServiceImplTest.* 20618: SAIL-371 (SAIL-294): NodeDAO: AuditableAspectTest (fix testCreateNodeWithAuditableProperties_ALF_2565 + add remove aspect test) 20624: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - UserUsageTest.* 20626: Fixed random keys for RuleTrigger NodeRef tracking 20635: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - PersonTest.testSplitDuplicates 20642: SAIL-371 (SAIL-294) DAO: Fixed CacheTest 20643: Removed must of the 'distribute' target's dependencies. Not for HEAD 20645: Follow-on to r20643 (Removed most of the 'distribute' target's dependencies. Not for HEAD) 20654: SAIL-371 (SAIL-294): NodeDAO: DMDeploymentTargetTest.* (do not try to remove mandatory aspects) 20655: SAIL-371 (SAIL-294): NodeDAO: Initial fix for TaggingServiceImplTest.testTagScopeUpdateViaNodePolicies (+ minor test cleanup) 20657: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - VersionMigratorTest.testMigrateOneVersion (cm:accessed not returned if null) 20658: Merged (back merge only - no merge info) BRANCHES/V3.3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 20090: Dynamic models: minor improvements to DictionaryModelType 20554: Improvement to model delete validation (investigating intermittent failure of RepoAdminServiceImplTest.testSimpleDynamicModelViaNodeService) 20662: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - RecordsManagementAuditServiceImplTest.* (we now ignore attempt to update 'cm:modifier' prop so update 'cm:title' prop instead) 20666: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - ADMLuceneTest.* 20668: SAIL-239 (SAIL-294) - delete WCM locks + tests (follow-on to r20060) 20674: SAIL-371 (SAIL-294) NodeDAO fallout: Cleaner and additional checks for ContentStoreCleaner 20675: SAIL-371 (SAIL-294) NodeDAO fallout: Fixed handling of ContentData git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20693 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
735 lines
29 KiB
Java
735 lines
29 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.node.index;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.TreeMap;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.repo.domain.node.Transaction;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.extensions.surf.util.ISO8601DateFormat;
|
|
|
|
/**
|
|
* Component to check and recover the indexes.
|
|
*
|
|
* @author Derek Hulley
|
|
*/
|
|
public class IndexTransactionTracker extends AbstractReindexComponent
|
|
{
|
|
private static Log logger = LogFactory.getLog(IndexTransactionTracker.class);
|
|
|
|
private IndexTransactionTrackerListener listener;
|
|
private NodeIndexer nodeIndexer;
|
|
|
|
private long maxTxnDurationMs;
|
|
private long reindexLagMs;
|
|
private int maxRecordSetSize;
|
|
private int maxTransactionsPerLuceneCommit;
|
|
private boolean disableInTransactionIndexing;
|
|
|
|
private boolean started;
|
|
private List<Long> previousTxnIds;
|
|
private Long lastMaxTxnId;
|
|
private long fromTimeInclusive;
|
|
private Map<Long, TxnRecord> voids;
|
|
private boolean forceReindex;
|
|
|
|
private long fromTxnId;
|
|
private String statusMsg;
|
|
private static final String NO_REINDEX = "No reindex in progress";
|
|
|
|
/**
|
|
* Set the defaults.
|
|
* <ul>
|
|
* <li><b>Maximum transaction duration:</b> 1 hour</li>
|
|
* <li><b>Reindex lag:</b> 1 second</li>
|
|
* <li><b>Maximum recordset size:</b> 1000</li>
|
|
* <li><b>Maximum transactions per Lucene commit:</b> 100</li>
|
|
* <li><b>Disable in-transaction indexing:</b> false</li>
|
|
* </ul>
|
|
*/
|
|
public IndexTransactionTracker()
|
|
{
|
|
maxTxnDurationMs = 3600L * 1000L;
|
|
reindexLagMs = 1000L;
|
|
maxRecordSetSize = 1000;
|
|
maxTransactionsPerLuceneCommit = 100;
|
|
disableInTransactionIndexing = false;
|
|
|
|
started = false;
|
|
previousTxnIds = Collections.<Long>emptyList();
|
|
lastMaxTxnId = Long.MAX_VALUE;
|
|
fromTimeInclusive = -1L;
|
|
voids = new TreeMap<Long, TxnRecord>();
|
|
forceReindex = false;
|
|
|
|
fromTxnId = 0L;
|
|
statusMsg = NO_REINDEX;
|
|
}
|
|
|
|
public synchronized void setListener(IndexTransactionTrackerListener listener)
|
|
{
|
|
this.listener = listener;
|
|
}
|
|
|
|
public void setNodeIndexer(NodeIndexer nodeIndexer)
|
|
{
|
|
this.nodeIndexer = nodeIndexer;
|
|
}
|
|
|
|
/**
|
|
* Set the expected maximum duration of transaction supported. This value is used to adjust the
|
|
* look-back used to detect transactions that committed. Values must be greater than zero.
|
|
*
|
|
* @param maxTxnDurationMinutes the maximum length of time a transaction will take in minutes
|
|
*
|
|
* @since 1.4.5, 2.0.5, 2.1.1
|
|
*/
|
|
public void setMaxTxnDurationMinutes(long maxTxnDurationMinutes)
|
|
{
|
|
if (maxTxnDurationMinutes < 1)
|
|
{
|
|
throw new AlfrescoRuntimeException("Maximum transaction duration must be at least one minute.");
|
|
}
|
|
this.maxTxnDurationMs = maxTxnDurationMinutes * 60L * 1000L;
|
|
}
|
|
|
|
/**
|
|
* Transaction tracking should lag by the average commit time for a transaction. This will minimize
|
|
* the number of holes in the transaction sequence. Values must be greater than zero.
|
|
*
|
|
* @param reindexLagMs the minimum age of a transaction to be considered by
|
|
* the index transaction tracking
|
|
*
|
|
* @since 1.4.5, 2.0.5, 2.1.1
|
|
*/
|
|
public void setReindexLagMs(long reindexLagMs)
|
|
{
|
|
if (reindexLagMs < 1)
|
|
{
|
|
throw new AlfrescoRuntimeException("Reindex lag must be at least 1 millisecond.");
|
|
}
|
|
this.reindexLagMs = reindexLagMs;
|
|
}
|
|
|
|
/**
|
|
* Set the number of transactions to request per query.
|
|
*/
|
|
public void setMaxRecordSetSize(int maxRecordSetSize)
|
|
{
|
|
this.maxRecordSetSize = maxRecordSetSize;
|
|
}
|
|
|
|
/**
|
|
* Set the number of transactions to process per Lucene write.
|
|
* Larger values generate less contention on the Lucene IndexInfo files.
|
|
*/
|
|
public void setMaxTransactionsPerLuceneCommit(int maxTransactionsPerLuceneCommit)
|
|
{
|
|
this.maxTransactionsPerLuceneCommit = maxTransactionsPerLuceneCommit;
|
|
}
|
|
|
|
/**
|
|
* Enable or disabled in-transaction indexing. Under certain circumstances, the system
|
|
* can run with only index tracking enabled - in-transaction indexing is not always
|
|
* required. The {@link NodeIndexer} is disabled when this component initialises.
|
|
*/
|
|
public void setDisableInTransactionIndexing(boolean disableInTransactionIndexing)
|
|
{
|
|
this.disableInTransactionIndexing = disableInTransactionIndexing;
|
|
}
|
|
|
|
/**
|
|
* @return Returns <tt>false</tt> always. Transactions are handled internally.
|
|
*/
|
|
@Override
|
|
protected boolean requireTransaction()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/** Worker callback for transactional use */
|
|
RetryingTransactionCallback<Long> getStartingCommitTimeWork = new RetryingTransactionCallback<Long>()
|
|
{
|
|
public Long execute() throws Exception
|
|
{
|
|
return getStartingTxnCommitTime();
|
|
}
|
|
};
|
|
/** Worker callback for transactional use */
|
|
RetryingTransactionCallback<Boolean> reindexWork = new RetryingTransactionCallback<Boolean>()
|
|
{
|
|
public Boolean execute() throws Exception
|
|
{
|
|
return reindexInTransaction();
|
|
}
|
|
};
|
|
|
|
public void resetFromTxn(long txnId)
|
|
{
|
|
if (logger.isInfoEnabled())
|
|
{
|
|
logger.info("resetFromTxn: " + txnId);
|
|
}
|
|
|
|
this.fromTxnId = txnId;
|
|
this.started = false; // this will cause index tracker to break out (so that it can be re-started)
|
|
}
|
|
|
|
@Override
|
|
protected void reindexImpl()
|
|
{
|
|
if (logger.isInfoEnabled())
|
|
{
|
|
logger.info("reindexImpl started: " + this);
|
|
}
|
|
|
|
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
if (!started)
|
|
{
|
|
// Disable in-transaction indexing
|
|
if (disableInTransactionIndexing && nodeIndexer != null)
|
|
{
|
|
logger.warn("In-transaction indexing is being disabled.");
|
|
nodeIndexer.setEnabled(false);
|
|
}
|
|
// Make sure that we start clean
|
|
voids.clear();
|
|
previousTxnIds = new ArrayList<Long>(maxRecordSetSize);
|
|
lastMaxTxnId = null; // So that it is ignored at first
|
|
|
|
if (this.fromTxnId != 0L)
|
|
{
|
|
if (logger.isInfoEnabled())
|
|
{
|
|
logger.info("reindexImpl: start fromTxnId: " + fromTxnId);
|
|
}
|
|
|
|
Long fromTxnCommitTime = getTxnCommitTime(this.fromTxnId);
|
|
|
|
if (fromTxnCommitTime == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fromTimeInclusive = fromTxnCommitTime;
|
|
}
|
|
else
|
|
{
|
|
fromTimeInclusive = retryingTransactionHelper.doInTransaction(getStartingCommitTimeWork, true, true);
|
|
}
|
|
|
|
fromTxnId = 0L;
|
|
started = true;
|
|
|
|
if (logger.isInfoEnabled())
|
|
{
|
|
logger.info(
|
|
"reindexImpl: start fromTimeInclusive: " +
|
|
ISO8601DateFormat.format(new Date(fromTimeInclusive)));
|
|
}
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
Boolean repeat = retryingTransactionHelper.doInTransaction(reindexWork, true, true);
|
|
// Only break out if there isn't any more work to do (for now)
|
|
if (repeat == null || repeat.booleanValue() == false)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// Wait for the asynchronous reindexing to complete
|
|
waitForAsynchronousReindexing();
|
|
|
|
if (logger.isTraceEnabled())
|
|
{
|
|
logger.trace("reindexImpl: completed: "+this);
|
|
}
|
|
|
|
statusMsg = NO_REINDEX;
|
|
}
|
|
|
|
private Long getTxnCommitTime(final long txnId)
|
|
{
|
|
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
RetryingTransactionCallback<Long> getTxnCommitTimeWork = new RetryingTransactionCallback<Long>()
|
|
{
|
|
public Long execute() throws Exception
|
|
{
|
|
Transaction txn = nodeDAO.getTxnById(txnId);
|
|
if (txn != null)
|
|
{
|
|
return txn.getCommitTimeMs();
|
|
}
|
|
|
|
logger.warn("Txn not found: "+txnId);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
return retryingTransactionHelper.doInTransaction(getTxnCommitTimeWork, true, true);
|
|
}
|
|
|
|
/**
|
|
* @return Returns <tt>true</tt> if the reindex process can exit otherwise <tt>false</tt> if
|
|
* a new transaction should be created and the process kicked off again
|
|
*/
|
|
private boolean reindexInTransaction()
|
|
{
|
|
List<Transaction> txns = null;
|
|
|
|
long toTimeExclusive = System.currentTimeMillis() - reindexLagMs;
|
|
|
|
// Check that the voids haven't been filled
|
|
long minLiveVoidTime = checkVoids();
|
|
if (minLiveVoidTime <= fromTimeInclusive)
|
|
{
|
|
// A void was discovered.
|
|
// We need to adjust the search time for transactions, i.e. hop back in time but
|
|
// this also entails a full build from that point on. So all previous transactions
|
|
// need to be reindexed.
|
|
fromTimeInclusive = minLiveVoidTime;
|
|
previousTxnIds.clear();
|
|
}
|
|
|
|
// get next transactions to index
|
|
txns = getNextTransactions(fromTimeInclusive, toTimeExclusive, previousTxnIds);
|
|
|
|
// If there are no transactions, then all the work is done
|
|
if (txns.size() == 0)
|
|
{
|
|
// We have caught up.
|
|
// There is no need to force reindexing until the next unindex transactions appear.
|
|
forceReindex = false;
|
|
return false;
|
|
}
|
|
|
|
statusMsg = String.format(
|
|
"Reindexing batch of %d transactions from %s (txnId=%s)",
|
|
txns.size(),
|
|
(new Date(fromTimeInclusive)).toString(),
|
|
txns.isEmpty() ? "---" : txns.get(0).getId().toString());
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(statusMsg);
|
|
}
|
|
|
|
// Reindex the transactions. Voids between the last set of transactions and this
|
|
// set will be detected as well. Additionally, the last max transaction will be
|
|
// updated by this method.
|
|
long maxProcessedTxnCommitTime = reindexTransactions(txns);
|
|
|
|
// Call the listener
|
|
synchronized (this)
|
|
{
|
|
if (listener != null)
|
|
{
|
|
listener.indexedTransactions(fromTimeInclusive, maxProcessedTxnCommitTime);
|
|
}
|
|
}
|
|
|
|
// Move the time on.
|
|
// The next fromTimeInclusive may well pull back transactions that have just been
|
|
// processed. But we keep track of those and exclude them from the results.
|
|
if (fromTimeInclusive == maxProcessedTxnCommitTime)
|
|
{
|
|
// The time didn't advance. If no new transaction appear, we could spin on
|
|
// two or more transactions with the same commit time. So we DON'T clear
|
|
// the list of previous transactions and we allow them to live on.
|
|
}
|
|
else
|
|
{
|
|
// The processing time has moved on
|
|
fromTimeInclusive = maxProcessedTxnCommitTime;
|
|
previousTxnIds.clear();
|
|
}
|
|
for (Transaction txn : txns)
|
|
{
|
|
previousTxnIds.add(txn.getId());
|
|
}
|
|
|
|
if (isShuttingDown() || (! started))
|
|
{
|
|
// break out if the VM is shutting down or tracker has been reset (ie. !started)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// There is more work to do and we should be called back right away
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public String getReindexStatus()
|
|
{
|
|
return statusMsg;
|
|
}
|
|
|
|
private static final long ONE_HOUR_MS = 3600*1000;
|
|
/**
|
|
* Find a transaction time to start indexing from (inclusive). The last recorded transaction by ID
|
|
* is taken and the max transaction duration substracted from its commit time. A transaction is
|
|
* retrieved for this time and checked for indexing. If it is present, then that value is chosen.
|
|
* If not, a step back in time is taken again. This goes on until there are no more transactions
|
|
* or a transaction is found in the index.
|
|
*/
|
|
protected long getStartingTxnCommitTime()
|
|
{
|
|
long now = System.currentTimeMillis();
|
|
// Get the last indexed transaction for all transactions
|
|
long lastIndexedAllCommitTimeMs = getLastIndexedCommitTime(now, false);
|
|
// Now check back from this time to make sure there are no remote transactions that weren't indexed
|
|
long lastIndexedRemoteCommitTimeMs = getLastIndexedCommitTime(now, true);
|
|
// The one to start at is the least of the two times
|
|
long startTime = Math.min(lastIndexedAllCommitTimeMs, lastIndexedRemoteCommitTimeMs);
|
|
// Done
|
|
// Make sure we recheck any voids
|
|
return startTime - maxTxnDurationMs;
|
|
}
|
|
/**
|
|
* Gets the commit time for the last indexed transaction. If there are no transactions, then the
|
|
* current time is returned.
|
|
*
|
|
* @param maxCommitTimeMs the largest commit time to consider
|
|
* @param remoteOnly <tt>true</tt> to only look at remotely-committed transactions
|
|
* @return Returns the last indexed transaction commit time for all or
|
|
* remote-only transactions.
|
|
*/
|
|
private long getLastIndexedCommitTime(long maxCommitTimeMs, boolean remoteOnly)
|
|
{
|
|
// Look back in time by the maximum transaction duration
|
|
long maxToTimeExclusive = maxCommitTimeMs - maxTxnDurationMs;
|
|
long toTimeExclusive = maxToTimeExclusive;
|
|
long fromTimeInclusive = 0L;
|
|
double stepFactor = 1.0D;
|
|
boolean firstWasInIndex = true;
|
|
found:
|
|
while (true)
|
|
{
|
|
// Get the most recent transaction before the given look-back
|
|
List<Transaction> nextTransactions = nodeDAO.getTxnsByCommitTimeDescending(
|
|
0L,
|
|
toTimeExclusive,
|
|
1,
|
|
null,
|
|
remoteOnly);
|
|
// There are no transactions in that time range
|
|
if (nextTransactions.size() == 0)
|
|
{
|
|
break found;
|
|
}
|
|
// We found a transaction
|
|
Transaction txn = nextTransactions.get(0);
|
|
long txnCommitTime = txn.getCommitTimeMs();
|
|
// Check that it is in the index
|
|
InIndex txnInIndex = isTxnPresentInIndex(txn);
|
|
switch (txnInIndex)
|
|
{
|
|
case YES:
|
|
fromTimeInclusive = txnCommitTime;
|
|
break found;
|
|
case INDETERMINATE:
|
|
// If we hit an indeterminate transaction we go back a small amount to try and hit something definitive before a bigger step back
|
|
firstWasInIndex = false;
|
|
toTimeExclusive = txnCommitTime - 1000;
|
|
continue;
|
|
default:
|
|
firstWasInIndex = false;
|
|
// Look further back in time. Step back by 60 seconds each time, increasing
|
|
// the step by 10% each iteration.
|
|
// Don't step back by more than an hour
|
|
long decrement = Math.min(ONE_HOUR_MS, (long) (60000.0D * stepFactor));
|
|
toTimeExclusive = txnCommitTime - decrement;
|
|
stepFactor *= 1.1D;
|
|
continue;
|
|
}
|
|
}
|
|
// If the last transaction (given the max txn duration) was in the index, then we used the
|
|
// maximum commit time i.e. the indexes were up to date up until the most recent time.
|
|
if (firstWasInIndex)
|
|
{
|
|
return maxToTimeExclusive;
|
|
}
|
|
else
|
|
{
|
|
return fromTimeInclusive;
|
|
}
|
|
}
|
|
|
|
private static final int VOID_BATCH_SIZE = 100;
|
|
/**
|
|
* Voids - otherwise known as 'holes' - in the transaction sequence are timestamped when they are
|
|
* discovered. This method discards voids that were timestamped before the given date. It checks
|
|
* all remaining voids, passing back the transaction time for the newly-filled void. Otherwise
|
|
* the value passed in is passed back.
|
|
*
|
|
* @return Returns an adjused start position based on any voids being filled
|
|
* or <b>Long.MAX_VALUE</b> if no new voids were found
|
|
*/
|
|
private long checkVoids()
|
|
{
|
|
long maxHistoricalTime = (fromTimeInclusive - maxTxnDurationMs);
|
|
long fromTimeAdjusted = Long.MAX_VALUE;
|
|
|
|
List<Long> toExpireTxnIds = new ArrayList<Long>(1);
|
|
Iterator<Long> voidTxnIdIterator = voids.keySet().iterator();
|
|
List<Long> voidTxnIdBatch = new ArrayList<Long>(VOID_BATCH_SIZE);
|
|
|
|
while (voidTxnIdIterator.hasNext())
|
|
{
|
|
Long voidTxnId = voidTxnIdIterator.next();
|
|
// Add it to the batch
|
|
voidTxnIdBatch.add(voidTxnId);
|
|
// If the batch is full or if there are no more voids, fire the query
|
|
if (voidTxnIdBatch.size() == VOID_BATCH_SIZE || !voidTxnIdIterator.hasNext())
|
|
{
|
|
List<Transaction> filledTxns = nodeDAO.getTxnsByCommitTimeAscending(voidTxnIdBatch);
|
|
for (Transaction txn : filledTxns)
|
|
{
|
|
if (txn.getCommitTimeMs() == null) // Just coping with Hibernate mysteries
|
|
{
|
|
continue;
|
|
}
|
|
else if (isTxnPresentInIndex(txn) != InIndex.NO)
|
|
{
|
|
// It is in the index so expire it from the voids.
|
|
// This can happen if void was committed locally.
|
|
toExpireTxnIds.add(txn.getId());
|
|
}
|
|
else
|
|
{
|
|
// It's not in the index so we have a timespamp from which to kick off
|
|
// It is a bone fide first transaction. A void has been filled.
|
|
long txnCommitTimeMs = txn.getCommitTimeMs().longValue();
|
|
// If the value is lower than our current one we keep it
|
|
if (txnCommitTimeMs < fromTimeAdjusted)
|
|
{
|
|
fromTimeAdjusted = txnCommitTimeMs;
|
|
}
|
|
// The query selected them in timestamp order so there is no need to process
|
|
// the remaining transactions in this batch - we have our minimum.
|
|
break;
|
|
}
|
|
}
|
|
// Wipe the batch clean
|
|
voidTxnIdBatch.clear();
|
|
}
|
|
// Check if the void must be expired or not
|
|
TxnRecord voidTxnRecord = voids.get(voidTxnId);
|
|
if (voidTxnRecord.txnCommitTime < maxHistoricalTime)
|
|
{
|
|
// It's too late for this void whether or not it has become live
|
|
toExpireTxnIds.add(voidTxnId);
|
|
}
|
|
}
|
|
// Throw away all the expired or removable voids
|
|
int voidCountBefore = voids.size();
|
|
for (Long toRemoveTxnId : toExpireTxnIds)
|
|
{
|
|
voids.remove(toRemoveTxnId);
|
|
}
|
|
int voidCountAfter = voids.size();
|
|
if (logger.isDebugEnabled() && voidCountBefore != voidCountAfter)
|
|
{
|
|
logger.debug("Void count " + voidCountBefore + " -> " + voidCountAfter);
|
|
}
|
|
// Done
|
|
if (logger.isDebugEnabled() && fromTimeAdjusted < Long.MAX_VALUE)
|
|
{
|
|
logger.debug("Returning to void time " + fromTimeAdjusted);
|
|
}
|
|
return fromTimeAdjusted;
|
|
}
|
|
|
|
private List<Transaction> getNextTransactions(long fromTimeInclusive, long toTimeExclusive, List<Long> previousTxnIds)
|
|
{
|
|
List<Transaction> txns = nodeDAO.getTxnsByCommitTimeAscending(
|
|
fromTimeInclusive,
|
|
toTimeExclusive,
|
|
maxRecordSetSize,
|
|
previousTxnIds,
|
|
false);
|
|
// done
|
|
return txns;
|
|
}
|
|
|
|
/**
|
|
* Checks that each of the transactions is present in the index. As soon as one is found that
|
|
* isn't, all the following transactions will be reindexed. After the reindexing, the sequence
|
|
* of transaction IDs will be examined for any voids. These will be recorded.
|
|
*
|
|
* @param txns transactions ordered by time ascending
|
|
* @return returns the commit time of the last transaction in the list
|
|
* @throws IllegalArgumentException if there are no transactions
|
|
*/
|
|
private long reindexTransactions(List<Transaction> txns)
|
|
{
|
|
if (txns.isEmpty())
|
|
{
|
|
throw new IllegalArgumentException("There are no transactions to process");
|
|
}
|
|
|
|
// Determines the window for void retention
|
|
long now = System.currentTimeMillis();
|
|
long oldestVoidRetentionTime = (now - maxTxnDurationMs);
|
|
|
|
// Keep an ordered map of IDs that we process along with their commit times
|
|
Map<Long, TxnRecord> processedTxnRecords = new TreeMap<Long, TxnRecord>();
|
|
|
|
List<Long> txnIdBuffer = new ArrayList<Long>(maxTransactionsPerLuceneCommit);
|
|
Iterator<Transaction> txnIterator = txns.iterator();
|
|
while (txnIterator.hasNext())
|
|
{
|
|
Transaction txn = txnIterator.next();
|
|
Long txnId = txn.getId();
|
|
Long txnCommitTimeMs = txn.getCommitTimeMs();
|
|
if (txnCommitTimeMs == null)
|
|
{
|
|
// What? But let's be cautious and treat this as a void
|
|
continue;
|
|
}
|
|
// Keep a record of it
|
|
TxnRecord processedTxnRecord = new TxnRecord();
|
|
processedTxnRecord.txnCommitTime = txnCommitTimeMs;
|
|
processedTxnRecords.put(txnId, processedTxnRecord);
|
|
// Remove this entry from the void list - it is not void
|
|
voids.remove(txnId);
|
|
|
|
// Reindex the transaction if we are forcing it or if it isn't in the index already
|
|
if (forceReindex || isTxnPresentInIndex(txn) == InIndex.NO)
|
|
{
|
|
// From this point on, until the tracker has caught up, all transactions need to be indexed
|
|
forceReindex = true;
|
|
// Add the transaction to the buffer of transactions that need processing
|
|
txnIdBuffer.add(txnId);
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Reindexing transaction: " + txn);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Reindex skipping transaction: " + txn);
|
|
}
|
|
}
|
|
|
|
if (isShuttingDown() || (! started))
|
|
{
|
|
// break out if the VM is shutting down or tracker has been reset (ie. !started)
|
|
break;
|
|
}
|
|
// Flush the reindex buffer, if it is full or if we are on the last transaction and there are no more
|
|
if (txnIdBuffer.size() >= maxTransactionsPerLuceneCommit || (!txnIterator.hasNext() && txnIdBuffer.size() > 0))
|
|
{
|
|
try
|
|
{
|
|
// We try the reindex, but for the sake of continuity, have to let it run on
|
|
reindexTransactionAsynchronously(txnIdBuffer);
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
logger.warn("\n" +
|
|
"Reindex of transactions failed: \n" +
|
|
" Transaction IDs: " + txnIdBuffer + "\n" +
|
|
" Error: " + e.getMessage(),
|
|
e);
|
|
}
|
|
// Clear the buffer
|
|
txnIdBuffer = new ArrayList<Long>(maxTransactionsPerLuceneCommit);
|
|
}
|
|
}
|
|
// Use the last ID from the previous iteration as our starting point
|
|
Long lastId = lastMaxTxnId;
|
|
long lastCommitTime = -1L;
|
|
// Walk the processed txn IDs
|
|
for (Map.Entry<Long, TxnRecord> entry : processedTxnRecords.entrySet())
|
|
{
|
|
Long processedTxnId = entry.getKey();
|
|
TxnRecord processedTxnRecord = entry.getValue();
|
|
boolean voidsAreYoungEnough = processedTxnRecord.txnCommitTime >= oldestVoidRetentionTime;
|
|
if (lastId != null && voidsAreYoungEnough)
|
|
{
|
|
int voidCount = 0;
|
|
// Iterate BETWEEN the last ID and the current one to find voids
|
|
// Only enter the loop if the current upper limit transaction is young enough to
|
|
// consider for voids.
|
|
for (long i = lastId.longValue() + 1; i < processedTxnId; i++)
|
|
{
|
|
// The voids are optimistically given the same transaction time as transaction with the
|
|
// largest ID. We only bother w
|
|
TxnRecord voidRecord = new TxnRecord();
|
|
voidRecord.txnCommitTime = processedTxnRecord.txnCommitTime;
|
|
voids.put(new Long(i), voidRecord);
|
|
voidCount++;
|
|
}
|
|
if (logger.isDebugEnabled()&& voidCount > 0)
|
|
{
|
|
logger.debug("Voids detected: " + voidCount + " in range [" + lastId + ", " + processedTxnId + "]");
|
|
}
|
|
}
|
|
lastId = processedTxnId;
|
|
lastCommitTime = processedTxnRecord.txnCommitTime;
|
|
}
|
|
// Having searched for the nodes, we've recorded all the voids. So move the lastMaxTxnId up.
|
|
lastMaxTxnId = lastId;
|
|
|
|
// Done
|
|
return lastCommitTime;
|
|
}
|
|
|
|
private class TxnRecord
|
|
{
|
|
private long txnCommitTime;
|
|
@Override
|
|
public String toString()
|
|
{
|
|
StringBuilder sb = new StringBuilder(128);
|
|
sb.append("TxnRecord")
|
|
.append("[time=").append(txnCommitTime <= 0 ? "---" : new Date(txnCommitTime))
|
|
.append("]");
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A callback that can be set to provide logging and other record keeping
|
|
*
|
|
* @author Derek Hulley
|
|
* @since 2.1.4
|
|
*/
|
|
public interface IndexTransactionTrackerListener
|
|
{
|
|
void indexedTransactions(long fromTimeInclusive, long toTimeExclusive);
|
|
}
|
|
} |