カスタム Evictor の作成

WebSphere® eXtreme Scale を使用して、カスタム除去の実装を作成できます。

Evictor インターフェースを実装して eXtreme Scale の共通プラグイン規則に従うような、カスタム Evictor を作成する必要があります。 インターフェースは、次のとおりです。
public interface Evictor
{
    void initialize(BackingMap map, EvictionEventCallback callback);
    void activate();
    void apply(LogSequence sequence);
    void deactivate();
    void destroy();
}
EvictionEventCallback インターフェースには、以下のメソッドがあります。
public interface EvictionEventCallback
{
    void evictMapEntries(List evictorDataList) throws ObjectGridException;
    void evictEntries(List keysToEvictList) throws ObjectGridException;
    void setEvictorData(Object key, Object data);
    Object getEvictorData(Object key);
}

EvictionEventCallback メソッドは、次のように Evictor プラグインによって、eXtreme Scale フレームワークを コールバックするために使用されます。

トランザクションが完了すると、eXtreme Scale は Evictor インターフェースの apply メソッドを呼び出します。 完了しているトランザクションによって取得されたすべてのトランザクション・ロックは、保持されなくなります。 そのため、複数のスレッドが同時に apply メソッドを呼び出し、各スレッドが別々のトランザクションを完了することも起こり得ます。 トランザクション・ロックは、完了しているトランザクションによって既に解放されているので、apply メソッドはそれ自体で同期化を行って、それがスレッド・セーフであることを保証する必要があります。

EvictorData インターフェースを実装して、evictEntries メソッドの代わりに evictMapEntries メソッドを使用する理由は、そのような時間帯をなくすことにあります。 次のイベント・シーケンスを考えてみましょう。
  1. トランザクション 1 が完了し、LogSequence で apply メソッドを呼び出して、キー 1 のマップ・エントリーを削除する。
  2. トランザクション 2 が完了し、LogSequence で apply メソッドを呼び出して、キー 1 の新規マップ・エントリーを挿入する。 つまり、トランザクション 2 は、トランザクション 1 が削除したマップ・エントリーを再作成します。

Evictor は、 トランザクションを実行するスレッドとは非同期で実行するので、その Evictor でキー 1 を除去する場合、Evictor は、トランザクション 1 が完了する前に存在していたマップ・エントリーを除去するか、トランザクション 2 が再作成したマップ・エントリーを除去する可能性があります。 時間帯をなくすため、どのバージョンのキー 1 のマップ・エントリーを除去するのかを明確にするために、setEvictorData メソッドに渡されるオブジェクトによって EvictorData インターフェースを実装します。マップ・エントリーの存続期間中は、同じ EvictorData インスタンスを使用します。そのマップ・エントリーが削除され、 次に別のトランザクションによって再作成されるときは、Evictor は、EvictorData 実装の新規インスタンスを使用する必要があります。Evictor は、EvictorData 実装および evictMapEntries メソッドを使用することにより、マップ・エントリーに関連付けられているキャッシュ・エントリーに正しい EvictorData インスタンスが含まれている場合に限り、 そのマップ・エントリーが除去されることを保証できます。

Evictor インターフェースと EvictonEventCallback インターフェースにより、アプリケーションは、ユーザー定義の除去アルゴリズムを実装する Evictor を接続することができます。 次のコードの断片は、Evictor インターフェースの initialize メソッドを実装する方法を示しています。
import com.ibm.websphere.objectgrid.BackingMap;
import com.ibm.websphere.objectgrid.plugins.EvictionEventCallback;
import com.ibm.websphere.objectgrid.plugins.Evictor;
import com.ibm.websphere.objectgrid.plugins.LogElement;
import com.ibm.websphere.objectgrid.plugins.LogSequence;
import java.util.LinkedList;
// Instance variables
private BackingMap bm;
private EvictionEventCallback evictorCallback;
private LinkedList queue;
private Thread evictorThread;
public void initialize(BackingMap map, EvictionEventCallback callback)
{
    bm = map;
    evictorCallback = callback;
    queue = new LinkedList();
    // spawn evictor thread
    evictorThread = new Thread( this );
    String threadName = "MyEvictorForMap−" + bm.getName();
    evictorThread.setName( threadName );
    evictorThread.start();
}

前掲のコードでは、 マップ・オブジェクトとコールバック・オブジェクトへの参照をインスタンス変数内に保存します。 これにより、これらのオブジェクトを apply メソッドと destroy メソッドで使用することができます。 この例では、最長未使用時間 (LRU) アルゴリズムを実装するための先入れ、先出し キューとして使用されるリンク・リストが作成されます。 スレッドが作成され、そのスレッドへの参照は、インスタンス変数として保持されます。 この参照を保持することにより、destroy メソッドは作成されたスレッド に割り込んで終了させることができます。

次のコード・スニペットは、コードをスレッド・セーフにするための同期要件を無視して、Evictor インターフェースの apply メソッドを実装する方法を示しています。
import com.ibm.websphere.objectgrid.BackingMap;
import com.ibm.websphere.objectgrid.plugins.EvictionEventCallback;
import com.ibm.websphere.objectgrid.plugins.Evictor;
import com.ibm.websphere.objectgrid.plugins.EvictorData;
import com.ibm.websphere.objectgrid.plugins.LogElement;
import com.ibm.websphere.objectgrid.plugins.LogSequence;

public void apply(LogSequence sequence)
{
    Iterator iter = sequence.getAllChanges();
    while ( iter.hasNext() )
    {
        LogElement elem = (LogElement)iter.next();
        Object key = elem.getKey();
        LogElement.Type type = elem.getType();
        if ( type == LogElement.INSERT )
        {
            // do insert processing here by adding to front of LRU queue.
            EvictorData data = new EvictorData(key);
            evictorCallback.setEvictorData(key, data);
            queue.addFirst( data );
        }
        else if ( type == LogElement.UPDATE || type == LogElement.FETCH || type == LogElement.TOUCH )
        {
            // do update processing here by moving EvictorData object to
            // front of queue.
            EvictorData data = evictorCallback.getEvictorData(key);
            queue.remove(data);
            queue.addFirst(data);
        }
        else if ( type == LogElement.DELETE || type == LogElement.EVICT )
        {
            // do remove processing here by removing EvictorData object
            // from queue.
            EvictorData data = evictorCallback.getEvictorData(key);
            if ( data == EvictionEventCallback.KEY_NOT_FOUND )
            {
                // Assumption here is your asynchronous evictor thread
                // evicted the map entry before this thread had a chance
                // to process the LogElement request. So you probably
                // need to do nothing when this occurs.
            }
            else
            {
                // Key was found. So process the evictor data.
                if ( data != null )
                {
                    // Ignore null returned by remove method since spawned
                    // evictor thread may have already removed it from queue.
                    // But we need this code in case it was not the evictor
                    // thread that caused this LogElement to occur.
                    queue.remove( data );
                }
                else
                {
                    // Depending on how you write you Evictor, this possibility
                    // may not exist or it may indicate a defect in your evictor
                    // due to improper thread synchronization logic.
                }
            }
        }
    }
}

apply メソッド内への挿入処理では、通常、EvictionEventCallback インターフェースの setEvictorData メソッドに渡される Evictor データ・オブジェクトが作成されます。 この Evictor は、LRU 実装を示すためのものなので、initialize メソッドで作成されたキューの前に EvictorData も追加されます。 apply メソッド内の更新処理は通常、apply メソッドの前の呼び出し (例えば、apply メソッドの挿入処理による) によって作成された Evictor データ・オブジェクトを更新します。 この Evictor は LRU 実装であるため、 EvictorData オブジェクトをその現在のキュー位置からキューの前方に移動させる必要があります。 作成された Evictor スレッドは、 最後のキュー・エレメントが LRU エントリーを表しているため、 キュー内の最後の EvictorData オブジェクトを除去します。 その前提として、EvictorData オブジェクトは getKey メソッドを持っています。 これによって、Evictor スレッドは、除去する必要があるエントリーのキーを認識します。 この例では、コードをスレッド・セーフにするための同期要件を無視していることに留意してください。 実際のカスタム Evictor は、同期と同期点の結果として発生するパフォーマンスの障害を取り扱っているため、 より複雑です。

以下のコード・スニペットは、initialize メソッドが作成した実行可能スレッドの destroy メソッドと run メソッドを示しています。
// Destroy method simply interrupts the thread spawned by the initialize method.
public void destroy()
{
    evictorThread.interrupt();
}

// Here is the run method of the thread that was spawned by the initialize method.
public void run()
{
    // Loop until destroy method interrupts this thread.
    boolean continueToRun = true;
    while ( continueToRun )
    {
        try
        {
            // Sleep for a while before sweeping over queue.
            // The sleepTime is a good candidate for a evictor
            // property to be set.
            Thread.sleep( sleepTime );
            int queueSize = queue.size();
            // Evict entries if queue size has grown beyond the
            // maximum size. Obviously, maximum size would
            // be another evictor property.
            int numToEvict = queueSize − maxSize;
            if ( numToEvict > 0 )
            {
                // Remove from tail of queue since the tail is the
                // least recently used entry.
                List evictList = new ArrayList( numToEvict );
                while( queueSize > ivMaxSize )
                {
                    EvictorData data = null;
                    try
                    {
                        EvictorData data = (EvictorData) queue.removeLast();
                        evictList.add( data );
                        queueSize = queue.size();
                    }
                    catch ( NoSuchElementException nse )
                    {
                        // The queue is empty.
                        queueSize = 0;
                    }
                }
                // Request eviction if key list is not empty.
                if ( ! evictList.isEmpty() )
                {
                    evictorCallback.evictMapEntries( evictList );
                }
            }
        }
        catch ( InterruptedException e )
        {
            continueToRun = false;
        }
    } // end while loop
} // end run method.

オプションの RollBackEvictor インターフェース

com.ibm.websphere.objectgrid.plugins.RollbackEvictor インターフェースは、オプションで、Evictor プラグインによって実装することができます。このインターフェースを実装することにより、 トランザクションがコミットされたときだけでなく、 トランザクションがロールバックされたときにも、Evictor を呼び出すことができます。
public interface RollbackEvictor
{
    void rollingBack( LogSequence ls );
}

apply メソッドは、トランザクションがコミットされたときにのみ呼び出されます。 トランザクションがロールバックされたとき、Evictor が RollbackEvictor インターフェースを実装している場合は、rollingBack メソッドが呼び出されます。RollbackEvictor インターフェースが実装されていない場合は、トランザクションがロールバックされても、apply メソッドおよび rollingBack メソッドは呼び出されません。