package voldemort.server.protocol.admin; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import voldemort.VoldemortException; import voldemort.client.protocol.pb.VAdminProto.UpdatePartitionEntriesRequest; import voldemort.server.StoreRepository; import voldemort.server.VoldemortConfig; import voldemort.store.ErrorCodeMapper; import voldemort.store.StorageEngine; import voldemort.store.StoreUtils; import voldemort.store.metadata.MetadataStore; import voldemort.utils.ByteArray; import voldemort.utils.NetworkClassLoader; import voldemort.versioning.Versioned; /** * The buffering is so that if we the stream contains multiple versions for the * same key, then we would want the storage to be updated with all the versions * atomically, to make sure client does not read a partial set of versions at * any point * * TODO with the removal of Donor based rebalancing, this class has little value * and should be removed. */ class BufferedUpdatePartitionEntriesStreamRequestHandler extends UpdatePartitionEntriesStreamRequestHandler { private static final int VALS_BUFFER_EXPECTED_SIZE = 5; /** * Current key being buffered. */ private ByteArray currBufferedKey; private List<Versioned<byte[]>> currBufferedVals; public BufferedUpdatePartitionEntriesStreamRequestHandler(UpdatePartitionEntriesRequest request, ErrorCodeMapper errorCodeMapper, VoldemortConfig voldemortConfig, StorageEngine<ByteArray, byte[], byte[]> storageEngine, StoreRepository storeRepository, NetworkClassLoader networkClassLoader, MetadataStore metadataStore) { super(request, errorCodeMapper, voldemortConfig, storageEngine, storeRepository, networkClassLoader, metadataStore); currBufferedKey = null; currBufferedVals = new ArrayList<Versioned<byte[]>>(VALS_BUFFER_EXPECTED_SIZE); } /** * Persists the current set of versions buffered for the current key into * storage, using the multiVersionPut api * * NOTE: Now, it could be that the stream broke off and has more pending * versions. For now, we simply commit what we have to disk. A better design * would rely on in-stream markers to do the flushing to storage. */ private void writeBufferedValsToStorage() { List<Versioned<byte[]>> obsoleteVals = storageEngine.multiVersionPut(currBufferedKey, currBufferedVals); // log Obsolete versions in debug mode if(logger.isDebugEnabled() && obsoleteVals.size() > 0) { logger.debug("updateEntries (Streaming multi-version-put) rejected these versions as obsolete : " + StoreUtils.getVersions(obsoleteVals) + " for key " + currBufferedKey); } currBufferedVals = new ArrayList<Versioned<byte[]>>(VALS_BUFFER_EXPECTED_SIZE); } private void writeBufferedValsToStorageIfAny() { if(currBufferedVals.size() > 0) { writeBufferedValsToStorage(); } } @Override public void close(DataOutputStream outputStream) throws IOException { writeBufferedValsToStorageIfAny(); super.close(outputStream); } @Override public void handleError(DataOutputStream outputStream, VoldemortException e) throws IOException { writeBufferedValsToStorageIfAny(); super.handleError(outputStream, e); } @Override protected void finalize() { super.finalize(); /* * Also check if we have any pending values being buffered. if so, flush * to storage. */ writeBufferedValsToStorageIfAny(); } @Override protected void processEntry(ByteArray key, Versioned<byte[]> value) throws IOException { // Check if the current key is same as the one before. if(currBufferedKey != null && !key.equals(currBufferedKey)) { // if not, write buffered values for the previous key to storage writeBufferedValsToStorage(); } currBufferedKey = key; currBufferedVals.add(value); } @Override protected String getHandlerName() { return "BufferedUpdateEntries"; } }