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

com.gemstone.gemfire.internal.cache.persistence.soplog.SortedBuffer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * 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. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.internal.cache.persistence.soplog;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

import com.gemstone.gemfire.internal.cache.persistence.soplog.SortedOplogFactory.SortedOplogConfiguration;
import com.gemstone.gemfire.internal.util.LogService;

/**
 * Provides an in-memory buffer to temporarily hold key/value pairs until they
 * can be flushed to disk.  Each buffer instance can be optionally associated
 * with a user-specified tag for identification purposes.
 * 
 * @param  the tag type
 * @author bakera
 */
public class SortedBuffer extends AbstractSortedReader {
  /** the logger */
  private final ComponentLogWriter logger;
  
  /** the tag */
  private final T tag;
  
  /** in-memory sorted key/vaue buffer */
  private final NavigableMap buffer;

  /** the stats */
  private final BufferStats stats;
  
  /** the metadata, set during flush */
  private final EnumMap metadata;
  
  /** the command to run (or defer) when the flush is complete */
  private Runnable flushAction;
  
  public SortedBuffer(SortedOplogConfiguration config, T tag) {
    assert config != null;
    assert tag != null;
    
    this.tag = tag;
    
    buffer = new ConcurrentSkipListMap(config.getComparator());
    stats = new BufferStats();
    metadata = new EnumMap(Metadata.class);
    
    String name = config.getName() + "#" + tag;
    logger = ComponentLogWriter.getSoplogLogWriter(name, LogService.logger());
  }
  
  /**
   * Returns the tag associated with the buffer.
   * @return the tag
   */
  public T getTag() {
    return tag;
  }
  
  @Override
  public String toString() {
    return logger.name();
  }
  
  /**
   * Adds a new value to the buffer.
   * @param key the key
   * @param value the value
   */
  public void put(byte[] key, byte[] value) {
    if (buffer.put(key, value) == null) {
      // ASSUMPTION: updates don't significantly change the value length
      // this lets us optimize statistics calculations
      stats.add(key.length, value.length);
    }
  }
  
  /**
   * Allows sorted iteration over the buffer contents.
   * @return the buffer entries
   */
  public Iterable> entries() {
    return buffer.entrySet();
  }
  
  /**
   * Returns the number of entries in the buffer.
   * @return the count
   */
  public int count() {
    return buffer.size();
  }
  
  /**
   * Returns the size of the data in bytes.
   * @return the data size
   */
  public long dataSize() {
    return stats.totalSize();
  }
  
  /**
   * Clears the buffer of all entries.
   */
  public void clear() {
    if (logger.fineEnabled()) {
      logger.fine("Clearing buffer");
    }
    
    buffer.clear();
    stats.clear();
    metadata.clear();
    
    synchronized (this) {
      flushAction = null;
    }
  }
  
  /**
   * Returns true if the flush completion has been deferred.
   * @return true if deferred
   */
  public synchronized boolean isDeferred() {
    return flushAction != null;
  }
  
  /**
   * Defers the flush completion to a later time.  This is used to ensure correct
   * ordering of soplogs during parallel flushes.
   * 
   * @param action the action to perform when ready
   */
  public synchronized void defer(Runnable action) {
    assert flushAction == null;
    
    if (logger.fineEnabled()) {
      logger.fine("Deferring flush completion");
    }
    flushAction = action;
  }
  
  /**
   * Completes the deferred flush operation.
   */
  public synchronized void complete() {
    assert flushAction != null;

    try {
      if (logger.fineEnabled()) {
        logger.fine("Completing deferred flush operation");
      }
      flushAction.run();
      
    } finally {
      flushAction = null;
    }
  }
  
  /**
   * Returns the buffer metadata.
   * @return the metadata
   */
  public synchronized EnumMap getMetadata() {
    return metadata;
  }
  
  /**
   * Returns the metadata value for the given key.
   * 
   * @param name the metadata name
   * @return the requested metadata
   */
  public synchronized byte[] getMetadata(Metadata name) {
    return metadata.get(name);
  }
  
  /**
   * Sets the metadata for the buffer.  This is not available until the buffer
   * is about to be flushed.
   * 
   * @param metadata the metadata
   */
  public synchronized void setMetadata(EnumMap metadata) {
    if (metadata != null) {
      this.metadata.putAll(metadata);
    }
  }
  
  @Override
  public boolean mightContain(byte[] key) {
    return true;
  }

  @Override
  public ByteBuffer read(byte[] key) throws IOException {
    byte[] val = buffer.get(key);
    if (val != null) {
      return ByteBuffer.wrap(val);
    }
    return null;
  }

  @Override
  public SortedIterator scan(
      byte[] from, boolean fromInclusive, 
      byte[] to, boolean toInclusive,
      boolean ascending,
      MetadataFilter filter) {

    if (filter == null || filter.accept(metadata.get(filter.getName()))) {
      NavigableMap subset = ascending ? buffer : buffer.descendingMap();
      if (from == null && to == null) {
        // we're good
      } else if (from == null) {
        subset = subset.headMap(to, toInclusive);
      } else if (to == null) {
        subset = subset.tailMap(from, fromInclusive);
      } else {
        subset = subset.subMap(from, fromInclusive, to, toInclusive);
      }
      return new BufferIterator(subset.entrySet().iterator());
    }
    return new BufferIterator(Collections.emptyMap().entrySet().iterator());
  }

  @Override
  public SerializedComparator getComparator() {
    return (SerializedComparator) buffer.comparator();
  }

  @Override
  public SortedStatistics getStatistics() {
    return stats;
  }
  
  @Override
  public void close() throws IOException {
    if (logger.fineEnabled()) {
      logger.fine("Closing buffer");
    }
    
    synchronized (this) {
      flushAction = null;
    }
  }
  
  /**
   * Allows sorted iteration over the buffer contents.
   */
  public static class BufferIterator implements SortedIterator
  {
    /** the backing iterator */
    private final Iterator> entries;
    
    /** the iteration cursor */
    private Entry current;
    
    public BufferIterator(Iterator> iterator) {
      this.entries = iterator;
    }
    
    @Override
    public ByteBuffer key() {
      return ByteBuffer.wrap(current.getKey());
    }

    @Override
    public ByteBuffer value() {
      return ByteBuffer.wrap(current.getValue());
    }

    @Override
    public void close() {
    }

    @Override
    public boolean hasNext() {
      return entries.hasNext();
    }

    @Override
    public ByteBuffer next() {
      current = entries.next();
      return key();
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }
  
  private class BufferStats implements SortedStatistics {
    /** data size */
    private long totalSize;
    
    /** key count */
    private long keys;
    
    /** avg key size */
    private double avgKeySize;
    
    /** avg value size */
    private double avgValueSize;
    
    private synchronized void clear() {
      totalSize = 0;
      keys = 0;
      avgKeySize = 0;
      avgValueSize = 0;
    }
    
    private synchronized void add(int keyLength, int valueLength) {
      totalSize += keyLength + valueLength;
      avgKeySize = (keyLength + keys * avgKeySize) / (keys + 1);
      avgValueSize = (keyLength + keys * avgValueSize) / (keys + 1);
      
      keys++;
    }
    
    @Override
    public synchronized long keyCount() {
      return keys;
    }

    @Override
    public byte[] firstKey() {
      return buffer.firstKey();
    }

    @Override
    public byte[] lastKey() {
      return buffer.lastKey();
    }

    @Override
    public synchronized double avgKeySize() {
      return avgKeySize;
    }
    
    @Override
    public synchronized double avgValueSize() {
      return avgValueSize;
    }
    
    @Override
    public void close() {
    }

    public synchronized long totalSize() {
      return totalSize;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy