package org.gbif.occurrence.persistence.keygen; import org.gbif.occurrence.common.config.OccHBaseConfiguration; import org.gbif.occurrence.persistence.api.KeyLookupResult; import org.gbif.occurrence.persistence.guice.ThreadLocalLockProvider; import org.gbif.occurrence.persistence.hbase.Columns; import java.util.Set; import com.google.inject.Inject; import org.apache.hadoop.hbase.client.Connection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkArgument; /** * An extension of AbstracHBaseKeyPersistenceService with a generateKey implementation that uses a thread local * ZookeeperLockManager to ensure thread-safe key gen. * * NOTE: this class is currently unused, as it becomes very slow when used concurrently by many threads. */ public class ZkLockingKeyService extends AbstractHBaseKeyPersistenceService { private static final Logger LOG = LoggerFactory.getLogger(ZkLockingKeyService.class); private final ThreadLocalLockProvider zookeeperLockManagerProvider; @Inject public ZkLockingKeyService(OccHBaseConfiguration cfg, Connection connection, ThreadLocalLockProvider zookeeperLockManagerProvider) { super(cfg, connection, new OccurrenceKeyBuilder()); this.zookeeperLockManagerProvider = zookeeperLockManagerProvider; } /** * Takes out a lock in zookeeper for the dataset of this occurrence before generating a key for it. It first checks * to see if a key already exists for this set of uniqueIdentifiers before generating a new key. * * @param uniqueStrings the identifiers that all refer to the same occurrence * * @return a KeyLookupResult with the key for this occurrence * * @throws IllegalArgumentException if the uniqueIdentifiers set is empty * @throws IllegalStateException if the next available key is greater than can be held in long */ @Override public KeyLookupResult generateKey(Set<String> uniqueStrings, String scope) { checkArgument(!uniqueStrings.isEmpty(), "uniqueIdentifiers can't be empty"); // if it already exists, return it right away KeyLookupResult findResult = findKey(uniqueStrings, scope); if (findResult != null) { LOG.debug("Asked to generate, but found existing."); return findResult; } LOG.debug("Waiting for lock"); zookeeperLockManagerProvider.get().waitForLock(scope); LOG.debug("Got lock"); // check again if it already exists, although now we expect it to be null - we have to have the lock for this // because if we check for existence before getting the lock then someone could write a new key and drop the lock // between us checking (finding nothing) and getting the lock and then writing a new (and now incorrect) key try { findResult = findKey(uniqueStrings, scope); if (findResult != null) { LOG.debug("{} Asked to generate, but found existing.",Thread.currentThread().getName()); return findResult; } // generate new key from counter table Long longKey = counterTableStore.incrementColumnValue(HBaseLockingKeyService.COUNTER_ROW, Columns.COUNTER_COLUMN, 1); if (longKey > Integer.MAX_VALUE) { throw new IllegalStateException( "The next available occurrence id is greater than what Integer can handle. This is fatal!"); } int newOccurrenceKey = longKey.intValue(); // build the lookup keys from the uniqueIdentifiers Set<String> lookupKeys = keyBuilder.buildKeys(uniqueStrings, scope); // write the new id to each of the lookup keys for (String key : lookupKeys) { lookupTableStore.putInt(key, Columns.LOOKUP_KEY_COLUMN, newOccurrenceKey); } return new KeyLookupResult(newOccurrenceKey, true); } finally { // in all cases we want to release the lock LOG.debug("Releasing lock"); zookeeperLockManagerProvider.get().releaseLock(scope); } } }