/* * Copyright 2014 University of Southern California * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.usc.pgroup.floe.flake.statemanager; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Output; import edu.usc.pgroup.floe.app.Tuple; import edu.usc.pgroup.floe.flake.StateSizeMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zeromq.ZMQ; import java.io.ByteArrayOutputStream; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author kumbhare */ public class ReducerStateManager extends StateManagerComponent implements PelletStateUpdateListener { /** * the global logger instance. */ private static final Logger LOGGER = LoggerFactory.getLogger(ReducerStateManager.class); /** * Key field name used by the reducer for grouping. */ private final String keyFieldName; /** * incr state size meter. */ private final Meter checkptSizemeter; /** * FUll state size meter. */ private final Meter fullSizemeter; /** * The pellet instance id to pellet state map. PelletState map is a map * from the custom key identifier to the pellet state. * private ConcurrentHashMap<String, HashMap<Object, PelletState>> pelletStateMap;*/ //To Fix #39. Since we do not know the pellet instance id during // recovery, we cannot associate state with a given pellet instance. // Further, we dont really use pe instance id since a state object is // associated with each key anyways. That was used only for perf. i guess. /** * PelletState map is a map from the reducer key identifier to the pellet * state. */ private ConcurrentHashMap<Object, PelletState> pelletStateMap; /** * State checkpointing component to periodically perform delta * checkpointing. */ private final StateCheckpointComponent checkpointer; /** * component to backup state as well as messages. */ private final ReducerStateBackupComponent backupComponent; /** * Constructor. * @param metricRegistry Metrics registry used to log various metrics. * @param flakeId Flake's id to which this component belongs. * @param componentName Unique name of the component. * @param ctx Shared zmq context. * @param fieldName The fieldName used by the reducer for grouping. * @param port Port to be used for sending checkpoint data. */ public ReducerStateManager(final MetricRegistry metricRegistry, final String flakeId, final String componentName, final ZMQ.Context ctx, final String fieldName, final int port) { super(metricRegistry, flakeId, componentName, ctx, port); this.pelletStateMap = new ConcurrentHashMap<>(); //fixme. add size, this.keyFieldName = fieldName; checkpointer = new StateCheckpointComponent(metricRegistry, flakeId, componentName + "-CHECKPOINTER", ctx, this, port); backupComponent = new ReducerStateBackupComponent(metricRegistry, flakeId, componentName + "-STBACKUP", ctx, fieldName); fullSizemeter = metricRegistry.meter(MetricRegistry.name( ReducerStateManager.class, "full.size")); checkptSizemeter = metricRegistry.meter(MetricRegistry.name( ReducerStateManager.class, "chkpt.size")); metricRegistry.register(MetricRegistry.name( ReducerStateManager.class, "ratio.size"), new StateSizeMonitor(fullSizemeter, checkptSizemeter)); } /** * Returns the object (state) associated with the given local pe instance. * The tuple may be used to further divide the state (e.g. in case of * reducer pellet, the tuple's key will be used to divide the state). * * @param peId Pellet's instance id. * @param tuple The tuple object which may be used to further divide the * state based on some criterion so that state * transfers/checkpointing may be improved. * @return pellet state corresponding to the given peId and tuple * combination. */ @Override public final PelletState getState(final String peId, final Tuple tuple) { //ignoring peId. if (tuple == null) { return null; } String value = (String) tuple.get(keyFieldName); return getState(peId, value); } /** * Returns the object (state) associated with the given local pe instance. * The tuple may be used to further divide the state (e.g. in case of * reducer pellet, the tuple's key will be used to divide the state). * * @param peId Pellet's instance id. * @param keyValue The value associated with the correspnding field name * (this is used during recovery since we * do not have access to the * entire tuple, but just the key). * @return pellet state corresponding to the given peId and key value * combination. */ @Override public final PelletState getState(final String peId, final String keyValue) { synchronized (pelletStateMap) { if (!pelletStateMap.containsKey(keyValue)) { LOGGER.info("Creating new state for key: {}", keyValue); pelletStateMap.put(keyValue, new PelletState(keyValue, this)); } } return pelletStateMap.get(keyValue); } /** * Checkpoint state and return the serialized delta to send to the backup * nodes. * * @return serialized delta to send to the backup nodes. */ @Override public final byte[] checkpointState() { //FIXME: MAKE THIS INTO A FACTORY. //KryoStateSerializer serializer = new KryoStateSerializer(); Kryo kryo = new Kryo(); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Output kryoOut = new Output(outStream); synchronized (pelletStateMap) { for (PelletState pState: pelletStateMap.values()) { LOGGER.debug("starting checkpointing:{}", pState.getCustomId()); if (pState.getLatestTimeStampAtomic() == -1) { continue; //do not checkpoint this state yet. } PelletStateDelta delta = pState.startDeltaCheckpointing(); LOGGER.debug("to checkpoint this:{}", delta.getDeltaState()); if (delta.getDeltaState().size() > 0) { //serializer.writeDeltaState(delta); kryo.writeObject(kryoOut, delta); } pState.finishDeltaCheckpointing(); } } kryoOut.flush(); byte[] chkpt = outStream.toByteArray(); checkptSizemeter.mark(chkpt.length); kryoOut.close(); return chkpt; //byte[] serialized = serializer.getBuffer(); //LOGGER.info("Serialied size:{}", serialized.length); //return serialized; } /** * Used to backup the states received from the neighbor flakes. * @param nfid flake id of the neighbor from which the state update is * received. * @param deltas a list of pellet state deltas received from the flake. */ @Override public final void backupState(final String nfid, final List<PelletStateDelta> deltas) { backupComponent.backupState(nfid, deltas); } /** * Get the state backed up for the given neighbor flake id. * * @param neighborFid neighbor's flake id. * @return the backed up state associated with the given fid * */ @Override public final Map<String, PelletStateDelta> getBackupState( final String neighborFid) { return backupComponent.getBackupState(neighborFid); } /** * Starts all the sub parts of the given component and notifies when * components starts completely. This will be in a different thread, * so no need to worry.. block as much as you want. * * @param terminateSignalReceiver terminate signal receiver. */ @Override protected final void runComponent( final ZMQ.Socket terminateSignalReceiver) { checkpointer.startAndWait(); backupComponent.startAndWait(); notifyStarted(true); terminateSignalReceiver.recv(); backupComponent.stopAndWait(); checkpointer.stopAndWait(); notifyStopped(true); } /** * Starts the msg recovery process for the given neighbor. * * @param nfid flake id of the neighbor for which the msg recovery is to * start. */ @Override public final void startMsgRecovery(final String nfid) { backupComponent.startMsgRecovery(nfid); } /** * @param srcPeId pellet instance id which resulted in this update * @param customId A custom identifier that can be used to further * identify this state's owner. * @param key the key for the state update. * @param value the updated value. * NOTE: THIS HAS TO BE THREAD SAFE.... */ @Override public final void stateUpdated(final String srcPeId, final Object customId, final String key, final Object value) { /*LOGGER.info("State updated for: {}, reducer key:{} , state key:{}, " + "new value:{}", srcPeId, customId, key, value);*/ } }