All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.net.cache.AbstractBundler Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

package com.tangosol.net.cache;


import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;

import java.util.ArrayList;
import java.util.List;

import com.oracle.coherence.common.base.Blocking;
import java.util.concurrent.atomic.AtomicInteger;


/**
* An abstract base for processors that implement bundling strategy.
* 

* Assume that we receive a continuous and concurrent stream of individual * operations on multiple threads in parallel. Let's also assume those individual * operations have relatively high latency (network or database-related) and * there are functionally analogous [bulk] operations that take a collection of * arguments instead of a single one without causing the latency to grow * linearly, as a function of the collection size. Examples of operations and * topologies that satisfy these assumptions are: *

    *
  • get() and getAll() methods for the {@link com.tangosol.net.NamedCache} * API for the partitioned cache service topology; *
  • put() and putAll() methods for the {@link com.tangosol.net.NamedCache} * API for the partitioned cache service topology; *
  • load() and loadAll() methods for the * {@link com.tangosol.net.cache.CacheLoader} API for the read-through * backing map topology; *
  • store() and storeAll() methods for the * {@link com.tangosol.net.cache.CacheStore} API for the write-through * backing map topology. *
*

* Under these assumptions, it's quite clear that the bundler could achieve a * better utilization of system resources and better throughput if slightly * delays the individual execution requests with a purpose of "bundling" them * together and passing into a corresponding bulk operation. Additionally, * the "bundled" request should be triggered if a bundle reaches a "preferred * bundle size" threshold, eliminating a need to wait till a bundle timeout is * reached. *

* Note: we assume that all bundle-able operations are idempotent and could be * repeated if un-bundling is necessary due to a bundled operation failure. * * @author gg 2007.01.28 * @since Coherence 3.3 */ public abstract class AbstractBundler extends Base { /** * Construct the bundler. By default, the timeout delay value is set to * one millisecond and the auto-adjustment feature is turned on. */ public AbstractBundler() { Bundle bundle = instantiateBundle(); bundle.setMaster(); m_listBundle.add(bundle); } // ----- property accessors ---------------------------------------------- /** * Obtain the bundle size threshold value. * * @return the bundle size threshold value expressed in the same units as the * value returned by the {@link Bundle#getBundleSize()} method */ public int getSizeThreshold() { return (int) m_dSizeThreshold; } /** * Specify the bundle size threshold value. * * @param cSize the bundle size threshold value; must be positive value * expressed in the same units as the value returned by the * {@link Bundle#getBundleSize()} method */ public void setSizeThreshold(int cSize) { if (cSize <= 0) { throw new IllegalArgumentException("Negative bundle size threshold"); } m_dSizeThreshold = cSize; // reset the previous value used for auto adjustment m_dPreviousSizeThreshold = 0.0; } /** * Obtains the minimum number of threads that will trigger the bundler to * switch from a pass through to a bundled mode. * * @return a the number of threads threshold */ public int getThreadThreshold() { return m_cThreadThreshold; } /** * Specify the minimum number of threads that will trigger the bundler to * switch from a pass through to a bundled mode. * * @param cThreads the number of threads threshold */ public void setThreadThreshold(int cThreads) { if (cThreads <= 0) { throw new IllegalArgumentException("Invalid thread threshold"); } m_cThreadThreshold = cThreads; } /** * Obtain the timeout delay value. * * @return the timeout delay value in milliseconds */ public long getDelayMillis() { return m_lDelayMillis; } /** * Specify the timeout delay value. * * @param lDelay the timeout delay value in milliseconds */ public void setDelayMillis(long lDelay) { if (lDelay <= 0) { throw new IllegalArgumentException("Invalid delay value"); } m_lDelayMillis = lDelay; } /** * Check whether or not the auto-adjustment is allowed. * * @return true iff the auto-adjustment is allowed */ public boolean isAllowAutoAdjust() { return m_fAllowAuto; } /** * Specify whether or not the auto-adjustment is allowed.. * * @param fAutoAdjust true if the auto-adjustment should be allowed; * false otherwise */ public void setAllowAutoAdjust(boolean fAutoAdjust) { m_fAllowAuto = fAutoAdjust; } // ----- statistics ------------------------------------------------------ /** * Update the statistics for this Bundle. */ protected void updateStatistics() { List listBundle = m_listBundle; Statistics stats = m_stats; while (true) { try { long cTotalBundles = 0l; long cTotalSize = 0l; long cTotalBurst = 0l; long cTotalWait = 0l; for (int i = 0, c = listBundle.size(); i < c; i++) { Bundle bundle = (Bundle) listBundle.get(i); cTotalBundles += bundle.m_cTotalBundles; cTotalSize += bundle.m_cTotalSize; cTotalBurst += bundle.m_cTotalBurstDuration; cTotalWait += bundle.m_cTotalWaitDuration; } long cDeltaBundles = cTotalBundles - stats.m_cBundleCountSnapshot; long cDeltaSize = cTotalSize - stats.m_cBundleSizeSnapshot; long cDeltaBurst = cTotalBurst - stats.m_cBurstDurationSnapshot; long cDeltaWait = cTotalWait - stats.m_cThreadWaitSnapshot; // log("DeltaBundles=" + cDeltaBundles + ", DeltaSize=" + cDeltaSize // + ", DeltaBurst=" + cDeltaBurst + ", DeltaWait=" + cDeltaWait); if (cDeltaBundles > 0 && cDeltaWait > 0) { stats.m_cAverageBundleSize = (int) Math.round( ((double) cDeltaSize) / ((double) cDeltaBundles)); stats.m_cAverageBurstDuration = (int) Math.round( ((double) cDeltaBurst) / ((double) cDeltaBundles)); stats.m_cAverageThreadWaitDuration = (int) Math.round( ((double) cDeltaWait) / ((double) cDeltaBundles)); stats.m_nAverageThroughput = (int) Math.round( ((double) cDeltaSize*1000) / (cDeltaWait)); } stats.m_cBundleCountSnapshot = cTotalBundles; stats.m_cBundleSizeSnapshot = cTotalSize; stats.m_cBurstDurationSnapshot = cTotalBurst; stats.m_cThreadWaitSnapshot = cTotalWait; return; } catch (IndexOutOfBoundsException e) { // there is theoretical possibility that the Java Memory Model // causes the list size to be not in sync with the actual list // storage; try again } } } /** * Reset this Bundler statistics. */ public void resetStatistics() { List listBundle = m_listBundle; while (true) { try { for (int i = 0, c = listBundle.size(); i < c; i++) { Bundle bundle = (Bundle) listBundle.get(i); bundle.resetStatistics(); } break; } catch (IndexOutOfBoundsException e) { // there is theoretical possibility that the memory model causes // the list size to be not in sync with the actual list storage; // try again } } m_stats.reset(); m_dPreviousSizeThreshold = 0.0; } /** * Adjust this Bundler's parameters according to the available statistical * information. */ public void adjust() { Statistics stats = m_stats; double dSizePrev = m_dPreviousSizeThreshold; double dSizeCurr = m_dSizeThreshold; int nThruPrev = stats.m_nAverageThroughput; updateStatistics(); int nThruCurr = stats.m_nAverageThroughput; // out("Size= " + (float) dSizePrev + ", Thru=" + nThruPrev + " -> " // + "Size= " + (float) dSizeCurr + ", Thru=" + nThruCurr); if (isAllowAutoAdjust()) { double dDelta = 0.0; if (dSizePrev == 0.0) { // the very first adjustment after reset dDelta = Math.max(1, 0.1*dSizeCurr); } else if (Math.abs(nThruCurr - nThruPrev) <= Math.max(1, (nThruCurr + nThruPrev)/100)) { // not more than 2% throughput change; // with a probability of 10% lets nudge the size up to 5% // in a random direction int nRandom = getRandom().nextInt(100); if (nRandom < 10 || Math.abs(dSizePrev - dSizeCurr) < 0.001) { dDelta = Math.max(1, 0.05*dSizeCurr); if (nRandom < 5) { dDelta = -dDelta; } } } else if (nThruCurr > nThruPrev) { // the throughput has improved; keep moving the size threshold // in the same direction at the same rate dDelta = (dSizeCurr - dSizePrev); } else { // the throughput has dropped; reverse the direction with half // of the previous rate dDelta = (dSizePrev - dSizeCurr) / 2; } if (dDelta != 0.0) { double dSizeNew = dSizeCurr + dDelta; if (dSizeNew > 1.0) { // out("Adjusting size by: " + // (float) dDelta + " to " + (float) dSizeNew); m_dPreviousSizeThreshold = dSizeCurr; m_dSizeThreshold = dSizeNew; } } } } /** * Provide a human readable description for the Bundler object * (for debugging). * * @return a human readable description for the Bundler object */ public String toString() { return ClassHelper.getSimpleName(getClass()) + "{SizeThreshold=" + getSizeThreshold() + ", ThreadThreshold=" + getThreadThreshold() + ", DelayMillis=" + getDelayMillis() + ", AutoAdjust=" + (isAllowAutoAdjust() ? "on" : "off") + ", ActiveBundles=" + m_listBundle.size() + ", Statistics=" + m_stats + "}"; } // ----- sublcassing support --------------------------------------------- /** * Retrieve any Bundle that is currently in the open state. This method does * not assume any external synchronization and as a result, a caller must * double check the returned bundle open state (after synchronizing on it). * * @return an open Bundle */ protected Bundle getOpenBundle() { List listBundle = m_listBundle; int cBundles = listBundle.size(); int iActiveBundle = m_iActiveBundle; try { for (int i = 0; i < cBundles; i++) { int iBundle = (iActiveBundle + i) % cBundles; Bundle bundle = (Bundle) listBundle.get(iBundle); if (bundle.isOpen()) { m_iActiveBundle = iBundle; return bundle; } } } catch (IndexOutOfBoundsException e) { // there is theoretical possibility that the memory model causes // the list size to be not in sync with the actual list storage; // proceed with synchronization... } catch (NullPointerException e) { // ditto } // we may need to create a new Bundle; synchronize to prevent the // creation of unnecessary bundles synchronized (listBundle) { // double check under synchronization cBundles = listBundle.size(); for (int i = 0; i < cBundles; i++) { int iBundle = (iActiveBundle + i) % cBundles; Bundle bundle = (Bundle) listBundle.get(iBundle); if (bundle.isOpen()) { m_iActiveBundle = iBundle; return bundle; } } // nothing available; add a new one Bundle bundle = instantiateBundle(); listBundle.add(bundle); m_iActiveBundle = cBundles; return bundle; } } /** * Instantiate a new Bundle object. * * @return a new Bundle object */ protected abstract Bundle instantiateBundle(); /** * Bundle represents a unit of optimized execution. */ protected abstract class Bundle extends Base { /** * Default constructor. */ protected Bundle() { m_iStatus = STATUS_OPEN; } // ----- accessors ------------------------------------------------- /** * Check whether or not this bundle is open for adding request elements. * * @return true iff this Bundle is still open */ protected boolean isOpen() { return m_iStatus == STATUS_OPEN; } /** * Check whether or not this bundle is in the "pending" state - awaiting * for the execution results. * * @return true iff this Bundle is in the "pending" state */ protected boolean isPending() { return m_iStatus == STATUS_PENDING; } /** * Check whether or not this bundle is in the "processed" state - * ready to return the result of execution back to the client. * * @return true iff this Bundle is in the "processed" state */ protected boolean isProcessed() { return m_iStatus == STATUS_PROCESSED || m_iStatus == STATUS_EXCEPTION; } /** * Check whether or not this bundle is in the "exception" state - * bundled execution threw an exception and requests have to be * un-bundled. * * @return true iff this Bundle is in the "exception" state */ protected boolean isException() { return m_iStatus == STATUS_EXCEPTION; } /** * Change the status of this Bundle. * * @param iStatus the new status value */ protected synchronized void setStatus(int iStatus) { boolean fValid; switch (m_iStatus) { case STATUS_OPEN: fValid = iStatus == STATUS_PENDING || iStatus == STATUS_EXCEPTION; break; case STATUS_PENDING: fValid = iStatus == STATUS_PROCESSED || iStatus == STATUS_EXCEPTION; break; case STATUS_PROCESSED: case STATUS_EXCEPTION: fValid = iStatus == STATUS_OPEN; break; default: fValid = false; } if (!fValid) { throw new IllegalStateException(this + "; invalid transition to " + formatStatusName(iStatus)); } m_iStatus = iStatus; if (iStatus == STATUS_PROCESSED || iStatus == STATUS_EXCEPTION) { m_cTotalWaitDuration += Math.max(0l, System.currentTimeMillis() - m_ldtStart); notifyAll(); } } /** * Obtain this bundle size. The return value should be expressed in the * same units as the value returned by the * {@link AbstractBundler#getSizeThreshold getSizeThreshold} method. * * @return the bundle size */ protected int getBundleSize() { return m_cThreads; } /** * Check whether or not this is a "master" Bundle. * * @return true iff this Bundle is a designated "master" Bundle */ protected boolean isMaster() { return m_fMaster; } /** * Designate this Bundle as a "master" bundle. */ protected void setMaster() { m_fMaster = true; } // ----- processing and subclassing support -------------------------- /** * Obtain results of the bundled requests. This method should be * implemented by concrete Bundle implementations using the most * efficient mechanism. */ protected abstract void ensureResults(); /** * Wait until results of bundled requests are retrieved. *

* Note that calls to this method must be externally synchronized. * * @param fFirst true iff this is the first thread entering the bundle * * @return true if this thread is supposed to perform an actual bundled * operation (burst); false otherwise */ protected boolean waitForResults(boolean fFirst) { m_cThreads++; try { if (fFirst) { m_ldtStart = System.currentTimeMillis(); } if (getBundleSize() < getSizeThreshold()) { if (fFirst) { long lDelay = getDelayMillis(); do { Blocking.wait(this, lDelay); // if someone has already "submitted" the bundle // need to keep waiting lDelay = 0l; } while (isPending()); } else { while (true) { Blocking.wait(this); if (isProcessed()) { return false; } // spurious wake-up; continue waiting } } } if (isProcessed()) { return false; } // this bundle should be closed and processed right away setStatus(STATUS_PENDING); // update stats m_cTotalSize += getBundleSize(); long cTotal = ++m_cTotalBundles; if (cTotal > 1000 // allow the "hotspot" to kick in && cTotal % ADJUSTMENT_FREQUENCY == 0 && isMaster()) { // attempt to adjust for every 1000 iterations of the master // bundle adjust(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); setStatus(STATUS_EXCEPTION); } catch (RuntimeException e) { // should never happen --m_cThreads; throw e; } catch (Error e) { // should never happen --m_cThreads; throw e; } return true; } /** * Obtain results of the bundled requests or ensure that the results * have already been retrieved. * * @param fBurst specifies whether or not the actual results have to be * fetched on this thread; this parameter will be true * for one and only one thread per bundle * * @return true if the bundling has succeeded; false if the un-bundling * has to be performed as a result of a failure */ protected boolean ensureResults(boolean fBurst) { if (isException()) { return false; } if (fBurst) { // bundle is closed and ready for the actual execution (burst); // it must be performed without holding any synchronization try { long ldtStart = System.currentTimeMillis(); ensureResults(); long cElapsedMillis = System.currentTimeMillis() - ldtStart; if (cElapsedMillis > 0) { m_cTotalBurstDuration += cElapsedMillis; } setStatus(STATUS_PROCESSED); } catch (Throwable e) { setStatus(STATUS_EXCEPTION); return false; } } else { azzert(isProcessed()); } return true; } /** * Release all bundle resources associated with the current thread. * * @return true iff all entered threads have released */ protected synchronized boolean releaseThread() { azzert(isProcessed() && m_cThreads > 0); if (--m_cThreads == 0) { setStatus(STATUS_OPEN); return true; } return false; } // ----- statistics and debugging ----------------------------------- /** * Reset statistics for this Bundle. */ public void resetStatistics() { m_cTotalBundles = 0l; m_cTotalSize = 0l; m_cTotalBurstDuration = 0l; m_cTotalWaitDuration = 0l; } /** * Provide a human readable description for the Bundle object * (for debugging). * * @return a human readable description for the Bundle object */ public String toString() { return "Bundle@" + hashCode() + "{" + formatStatusName(m_iStatus) + ", size=" + getBundleSize() + '}'; } /** * Return a human readable name for the specified status value. * * @param iStatus the status value to format * * @return a human readable status name */ protected String formatStatusName(int iStatus) { switch (iStatus) { case STATUS_OPEN: return "STATUS_OPEN"; case STATUS_PENDING: return "STATUS_PENDING"; case STATUS_PROCESSED: return "STATUS_PROCESSED"; case STATUS_EXCEPTION: return "STATUS_EXCEPTION"; default: return "unknown"; } } // ----- data fields and constants --------------------------------- /** * This Bundle accepting additional items. */ public static final int STATUS_OPEN = 0; /** * This Bundle is closed for accepting additional items and awaiting * for the execution results. */ public static final int STATUS_PENDING = 1; /** * This Bundle is in process of returning the result of execution * back to the client. */ public static final int STATUS_PROCESSED = 2; /** * Attempt to bundle encountered and exception; the execution has to be * de-optimized and performed by individual threads. */ public static final int STATUS_EXCEPTION = 3; /** * This Bundle status. */ private volatile int m_iStatus = STATUS_OPEN; /** * A count of threads that are using this Bundle. */ private int m_cThreads; /** * A flag that differentiates the "master" bundle which is responsible * for all auto-adjustments. It's set to "true" for one and only one * Bundle object. */ private boolean m_fMaster; // stat fields intentionally have the "package private" access to // prevent generation of synthetic access methods /** * Statistics: the total number of times this Bundle has been used for * bundled request processing. */ volatile long m_cTotalBundles; /** * Statistics: the total size of individual requests processed by this * Bundle expressed in the same units as values returned by the * {@link Bundle#getBundleSize()} method. */ volatile long m_cTotalSize; /** * Statistics: a timestamp of the first thread entering the bundle. */ private long m_ldtStart; /** * Statistics: a total time duration this Bundle has spent in bundled * request processing (burst). */ volatile long m_cTotalBurstDuration; /** * Statistics: a total time duration this Bundle has spent waiting for * bundle to be ready for processing. */ volatile long m_cTotalWaitDuration; } /** * Statistics class contains the latest bundler statistics. */ protected static class Statistics { /** * Reset the statistics. */ protected void reset() { m_cBundleCountSnapshot = 0l; m_cBundleSizeSnapshot = 0l; m_cBurstDurationSnapshot = 0l; m_cThreadWaitSnapshot = 0l; } /** * Provide a human readable description for the Statistics object. * (for debugging). * * @return a human readable description for the Statistics object */ public String toString() { return "(AverageBundleSize=" + m_cAverageBundleSize + ", AverageBurstDuration=" + m_cAverageBurstDuration + "ms" + ", AverageWaitDuration=" + m_cAverageThreadWaitDuration + "ms" + ", AverageThroughput=" + m_nAverageThroughput + "/sec" + ")"; } // ----- running averages ------------------------------------------ /** * An average time for bundled request processing (burst). */ protected int m_cAverageBurstDuration; /** * An average bundle size for this Bundler. */ protected int m_cAverageBundleSize; /** * An average thread waiting time caused by under-filled bundle. The * wait time includes the time spend in the bundled request processing. */ protected int m_cAverageThreadWaitDuration; /** * An average bundled request throughput in size units per millisecond * (total bundle size over total processing time) */ protected int m_nAverageThroughput; // ----- snapshots -------------------------------------------------- /** * Snapshot for a total number of processed bundled. */ protected long m_cBundleCountSnapshot; /** * Snapshot for a total size of processed bundled. */ protected long m_cBundleSizeSnapshot; /** * Snapshot for a burst duration. */ protected long m_cBurstDurationSnapshot; /** * Snapshot for a combined thread waiting time. */ protected long m_cThreadWaitSnapshot; } // ----- constants and data fields --------------------------------------- /** * Frequency of the adjustment attempts. This number represents a number of * iterations of the master bundle usage after which an adjustment attempt * will be performed. */ public static int ADJUSTMENT_FREQUENCY = 128; /** * The bundle size threshold. We use double for this value to allow for * fine-tuning of the auto-adjust algorithm. * * @see #adjust() */ private double m_dSizeThreshold; /** * The previous bundle size threshold value. */ protected double m_dPreviousSizeThreshold; /** * The minimum number of threads that should trigger the bundler to switch * from a pass through mode to a bundled mode. */ private int m_cThreadThreshold; /** * Specifies whether or not auto-adjustment is on. Default value is "true". */ private boolean m_fAllowAuto = true; /** * The delay timeout in milliseconds. Default value is one millisecond. */ private long m_lDelayMillis = 1l; /** * A pool of Bundle objects. Note that this list never shrinks. */ protected List m_listBundle = new ArrayList(); /** * Last active (open) bundle position. */ private volatile int m_iActiveBundle; /** * A counter for the total number of threads that have started any bundle * related execution. This counter is used by subclasses to reduce an impact * of bundled execution for lightly loaded environments. */ protected AtomicInteger m_countThreads = new AtomicInteger(); /** * An instance of the Statistics object containing the latest statistics. */ private Statistics m_stats = new Statistics(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy