package com.jivesoftware.os.amza.service.storage;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.jivesoftware.os.amza.api.partition.PartitionName;
import com.jivesoftware.os.amza.api.partition.PartitionProperties;
import com.jivesoftware.os.amza.api.partition.VersionedPartitionName;
import com.jivesoftware.os.amza.service.IndexedWALStorageProvider;
import com.jivesoftware.os.amza.service.StripingLocksProvider;
import com.jivesoftware.os.amza.service.stats.AmzaStats;
import com.jivesoftware.os.jive.utils.collections.lh.ConcurrentLHash;
import com.jivesoftware.os.jive.utils.ordered.id.TimestampedOrderIdProvider;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import java.io.File;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
/**
* @author jonathan.colt
*/
public class PartitionIndex {
private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
// TODO consider replacing ConcurrentHashMap<Long, PartitionStore> LHash
private final ConcurrentMap<PartitionName, ConcurrentLHash<PartitionStore>> partitionStores = Maps.newConcurrentMap();
private final StripingLocksProvider<VersionedPartitionName> locksProvider = new StripingLocksProvider<>(1024); // TODO expose to config
private final AmzaStats amzaSystemStats;
private final AmzaStats amzaStats;
private final TimestampedOrderIdProvider orderIdProvider;
private final IndexedWALStorageProvider walStorageProvider;
private final int concurrency;
private final ExecutorService partitionLoadExecutorService;
public PartitionIndex(AmzaStats amzaSystemStats,
AmzaStats amzaStats,
TimestampedOrderIdProvider orderIdProvider,
IndexedWALStorageProvider walStorageProvider,
int concurrency,
ExecutorService partitionLoadExecutorService) {
this.amzaSystemStats = amzaSystemStats;
this.amzaStats = amzaStats;
this.orderIdProvider = orderIdProvider;
this.walStorageProvider = walStorageProvider;
this.concurrency = concurrency;
this.partitionLoadExecutorService = partitionLoadExecutorService;
}
public void start() {
}
public void stop() {
partitionLoadExecutorService.shutdownNow();
}
public PartitionStore getIfPresent(VersionedPartitionName versionedPartitionName) {
ConcurrentLHash<PartitionStore> versionedStores = partitionStores.get(versionedPartitionName.getPartitionName());
if (versionedStores != null) {
return versionedStores.get(versionedPartitionName.getPartitionVersion());
}
return null;
}
public PartitionStore get(String context, VersionedPartitionName versionedPartitionName, PartitionProperties properties, int stripe) throws Exception {
return getAndValidate(context, -1, -1, versionedPartitionName, properties, stripe);
}
public PartitionStore getAndValidate(String context,
long deltaWALId,
long prevDeltaWALId,
VersionedPartitionName versionedPartitionName,
PartitionProperties properties,
int stripe) throws Exception {
if (properties == null) {
return null;
}
PartitionName partitionName = versionedPartitionName.getPartitionName();
if (deltaWALId > -1 && partitionName.isSystemPartition()) {
throw new IllegalStateException("Hooray you have a bug! Should never call get with something other than -1 for system partitions: " + deltaWALId);
}
ConcurrentLHash<PartitionStore> versionedStores = partitionStores.get(partitionName);
if (versionedStores != null) {
PartitionStore partitionStore = versionedStores.get(versionedPartitionName.getPartitionVersion());
if (partitionStore != null) {
File baseKey = walStorageProvider.baseKey(versionedPartitionName, stripe);
partitionStore.load(baseKey, deltaWALId, prevDeltaWALId, stripe, partitionLoadExecutorService);
return partitionStore;
}
}
if (!versionedPartitionName.getPartitionName().isSystemPartition()
&& !getSystemPartition(PartitionCreator.REGION_INDEX).containsKey(null, partitionName.toBytes())) {
return null;
}
return init(context, deltaWALId, prevDeltaWALId, versionedPartitionName, stripe, properties);
}
public PartitionStore getSystemPartition(VersionedPartitionName versionedPartitionName) {
Preconditions.checkArgument(versionedPartitionName.getPartitionName().isSystemPartition(), "Should only be called by system partitions.");
ConcurrentLHash<PartitionStore> versionedPartitionStores = partitionStores.get(versionedPartitionName.getPartitionName());
PartitionStore store = versionedPartitionStores == null ? null : versionedPartitionStores.get(0L);
if (store == null) {
throw new IllegalStateException("There is no system partition for " + versionedPartitionName);
}
return store;
}
public void delete(VersionedPartitionName versionedPartitionName, int stripe) throws Exception {
ConcurrentLHash<PartitionStore> versionedStores = partitionStores.get(versionedPartitionName.getPartitionName());
if (versionedStores != null) {
PartitionStore partitionStore = versionedStores.get(versionedPartitionName.getPartitionVersion());
if (partitionStore != null) {
File baseKey = walStorageProvider.baseKey(versionedPartitionName, stripe);
partitionStore.delete(baseKey);
versionedStores.remove(versionedPartitionName.getPartitionVersion());
}
}
}
private PartitionStore init(String context,
long deltaWALId,
long prevDeltaWALId,
VersionedPartitionName versionedPartitionName,
int stripe,
PartitionProperties properties) throws Exception {
synchronized (locksProvider.lock(versionedPartitionName, 1234)) {
ConcurrentLHash<PartitionStore> versionedStores = partitionStores.computeIfAbsent(versionedPartitionName.getPartitionName(),
(key) -> new ConcurrentLHash<>(3, -1, -2, concurrency));
PartitionStore partitionStore = versionedStores.get(versionedPartitionName.getPartitionVersion());
if (partitionStore != null) {
return partitionStore;
}
File baseKey = walStorageProvider.baseKey(versionedPartitionName, stripe);
WALStorage<?> walStorage = walStorageProvider.create(versionedPartitionName, properties);
partitionStore = new PartitionStore(versionedPartitionName.getPartitionName().isSystemPartition() ? amzaSystemStats : amzaStats,
orderIdProvider, versionedPartitionName, walStorage, properties);
partitionStore.load(baseKey, deltaWALId, prevDeltaWALId, stripe, partitionLoadExecutorService);
versionedStores.put(versionedPartitionName.getPartitionVersion(), partitionStore);
LOG.info("Opened partition:" + versionedPartitionName);
LOG.inc("open>context>" + context);
return partitionStore;
}
}
public boolean exists(String context, VersionedPartitionName versionedPartitionName, PartitionProperties properties, int stripe) throws Exception {
return get(context, versionedPartitionName, properties, stripe) != null;
}
public void updateStoreProperties(PartitionName partitionName, PartitionProperties properties) throws Exception {
ConcurrentLHash<PartitionStore> versionedPartitionStores = partitionStores.get(partitionName);
if (versionedPartitionStores != null) {
versionedPartitionStores.stream((long key, PartitionStore store) -> {
store.updateProperties(properties);
return true;
});
}
}
public interface PartitionStream {
boolean stream(VersionedPartitionName versionedPartitionName) throws Exception;
}
public void streamActivePartitions(PartitionStream stream) throws Exception {
if (stream != null) {
for (Entry<PartitionName, ConcurrentLHash<PartitionStore>> entry : partitionStores.entrySet()) {
if (!entry.getValue().stream((key, partitionStore) -> stream.stream(new VersionedPartitionName(entry.getKey(), key)))) {
break;
}
}
}
}
public interface PartitionPropertiesStream {
boolean stream(PartitionName partitionName, PartitionProperties partitionProperties) throws Exception;
}
public void invalidate(PartitionName partitionName) {
partitionStores.remove(partitionName);
}
}