package voldemort.store.retention; import java.util.Iterator; import java.util.List; import java.util.Map; import voldemort.VoldemortException; import voldemort.routing.RoutingStrategy; import voldemort.store.DelegatingStore; import voldemort.store.Store; import voldemort.store.StoreDefinition; import voldemort.store.StoreUtils; import voldemort.store.metadata.MetadataStoreListener; import voldemort.utils.ByteArray; import voldemort.utils.Time; import voldemort.versioning.VectorClock; import voldemort.versioning.Versioned; /** * Wraps the storage layer and ensures we don't return any values that are * stale. Optionally, deletes the expired versions. * */ public class RetentionEnforcingStore extends DelegatingStore<ByteArray, byte[], byte[]> implements MetadataStoreListener { private volatile StoreDefinition storeDef; private boolean deleteExpiredEntries; private volatile long retentionTimeMs; private Time time; public RetentionEnforcingStore(Store<ByteArray, byte[], byte[]> innerStore, StoreDefinition storeDef, boolean deleteExpiredEntries, Time time) { super(innerStore); updateStoreDefinition(storeDef); this.deleteExpiredEntries = deleteExpiredEntries; this.time = time; } @Override public void updateRoutingStrategy(RoutingStrategy routingStrategyMap) { return; // no-op } /** * Updates the store definition object and the retention time based on the * updated store definition */ @Override public void updateStoreDefinition(StoreDefinition storeDef) { this.storeDef = storeDef; if(storeDef.hasRetentionPeriod()) this.retentionTimeMs = storeDef.getRetentionDays() * Time.MS_PER_DAY; } /** * Performs the filtering of the expired entries based on retention time. * Optionally, deletes them also * * @param key the key whose value is to be deleted if needed * @param vals set of values to be filtered out * @return filtered list of values which are currently valid */ private List<Versioned<byte[]>> filterExpiredEntries(ByteArray key, List<Versioned<byte[]>> vals) { Iterator<Versioned<byte[]>> valsIterator = vals.iterator(); while(valsIterator.hasNext()) { Versioned<byte[]> val = valsIterator.next(); VectorClock clock = (VectorClock) val.getVersion(); // omit if expired if(clock.getTimestamp() < (time.getMilliseconds() - this.retentionTimeMs)) { valsIterator.remove(); // delete stale value if configured if(deleteExpiredEntries) { getInnerStore().delete(key, clock); } } } return vals; } @Override public Map<ByteArray, List<Versioned<byte[]>>> getAll(Iterable<ByteArray> keys, Map<ByteArray, byte[]> transforms) throws VoldemortException { StoreUtils.assertValidKeys(keys); Map<ByteArray, List<Versioned<byte[]>>> results = getInnerStore().getAll(keys, transforms); if(!storeDef.hasRetentionPeriod()) return results; for(ByteArray key: results.keySet()) { List<Versioned<byte[]>> filteredVals = filterExpiredEntries(key, results.get(key)); // remove/update the entry for the key, depending on how much is // filtered if(!filteredVals.isEmpty()) results.put(key, filteredVals); else results.remove(key); } return results; } @Override public List<Versioned<byte[]>> get(ByteArray key, byte[] transforms) throws VoldemortException { StoreUtils.assertValidKey(key); List<Versioned<byte[]>> vals = getInnerStore().get(key, transforms); if(!storeDef.hasRetentionPeriod()) return vals; return filterExpiredEntries(key, vals); } }