org.kairosdb.datastore.cassandra.WriteBuffer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kairosdb Show documentation
Show all versions of kairosdb Show documentation
KairosDB is a time series database that stores numeric values along
with key/value tags to a nosql data store. Currently supported
backends are Cassandra and H2. An H2 implementation is provided
for development work.
/*
* Copyright 2013 Proofpoint Inc.
*
* 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 org.kairosdb.datastore.cassandra;
import me.prettyprint.cassandra.model.HColumnImpl;
import me.prettyprint.cassandra.model.MutatorImpl;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.Serializer;
import me.prettyprint.hector.api.mutation.Mutator;
import org.kairosdb.util.Triple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class WriteBuffer implements Runnable
{
public static final Logger logger = LoggerFactory.getLogger(WriteBuffer.class);
private Keyspace m_keyspace;
private String m_cfName;
private List> m_buffer;
private Mutator m_mutator;
private volatile int m_bufferCount = 0;
private ReentrantLock m_mutatorLock;
private Condition m_lockCondition;
private Thread m_writeThread;
private boolean m_exit = false;
private int m_writeDelay;
private Serializer m_rowKeySerializer;
private Serializer m_columnKeySerializer;
private Serializer m_valueSerializer;
private WriteBufferStats m_writeStats;
private int m_maxBufferSize;
private int m_initialMaxBufferSize;
private ExecutorService m_executorService;
public WriteBuffer(Keyspace keyspace, String cfName,
int writeDelay, int maxWriteSize, Serializer keySerializer,
Serializer columnKeySerializer,
Serializer valueSerializer,
WriteBufferStats stats,
ReentrantLock mutatorLock,
Condition lockCondition,
int threadCount)
{
m_executorService = Executors.newFixedThreadPool(threadCount);
m_keyspace = keyspace;
m_cfName = cfName;
m_writeDelay = writeDelay;
m_initialMaxBufferSize = m_maxBufferSize = maxWriteSize;
m_rowKeySerializer = keySerializer;
m_columnKeySerializer = columnKeySerializer;
m_valueSerializer = valueSerializer;
m_writeStats = stats;
m_mutatorLock = mutatorLock;
m_lockCondition = lockCondition;
m_buffer = new ArrayList>();
m_mutator = new MutatorImpl(keyspace, keySerializer);
m_writeThread = new Thread(this);
m_writeThread.start();
}
/**
* Add a datapoint without a TTL.
* This datapoint will never be automatically deleted
*/
public void addData(
RowKeyType rowKey,
ColumnKeyType columnKey,
ValueType value,
long timestamp)
{
addData(rowKey, columnKey, value, timestamp, 0);
}
/**
* Add a datapoint with a TTL.
* This datapoint will be removed after ttl seconds
*/
public void addData(
RowKeyType rowKey,
ColumnKeyType columnKey,
ValueType value,
long timestamp,
int ttl)
{
/*if (true)
return;*/
m_mutatorLock.lock();
try
{
waitOnBufferFull();
m_bufferCount ++;
if (columnKey.toString().length() > 0)
{
m_buffer.add(new Triple(rowKey, columnKey, value, timestamp, ttl));
} else
{
logger.info("Discarded "+m_cfName+" row with empty column name. This should never happen.");
}
}
finally
{
m_mutatorLock.unlock();
}
}
public void deleteRow(RowKeyType rowKey, long timestamp)
{
m_mutatorLock.lock();
try
{
waitOnBufferFull();
m_bufferCount ++;
m_mutator.addDeletion(rowKey, m_cfName, timestamp);
}
finally
{
m_mutatorLock.unlock();
}
}
public void deleteColumn(RowKeyType rowKey, ColumnKeyType columnKey, long timestamp)
{
m_mutatorLock.lock();
try
{
waitOnBufferFull();
m_bufferCount ++;
m_mutator.addDeletion(rowKey, m_cfName, columnKey, m_columnKeySerializer, timestamp);
// m_mutator.delete(rowKey, m_cfName, columnKey, m_columnKeySerializer, timestamp);
}
finally
{
m_mutatorLock.unlock();
}
}
private void waitOnBufferFull()
{
if ((m_bufferCount > m_maxBufferSize) && (m_mutatorLock.getHoldCount() == 1))
{
//try
//{
//System.out.println("++++++Thread Interrupt+++++++++");
//m_writeThread.interrupt();
//m_lockCondition.await();
submitJob();
//}
//catch (InterruptedException ignored) {}
}
}
public void close() throws InterruptedException
{
m_exit = true;
m_writeThread.interrupt();
m_writeThread.join();
}
/**
This will slowly increase the max buffer size up to the initial size.
The design is that this method is called periodically to correct 3/4
throttling that occurs down below.
*/
public void increaseMaxBufferSize()
{
if (m_maxBufferSize < m_initialMaxBufferSize)
{
m_maxBufferSize += 1000;
logger.info("Increasing write buffer " + m_cfName + " size to "+m_maxBufferSize);
}
}
private void submitJob()
{
Mutator pendingMutations = null;
List> buffer = null;
m_writeStats.saveWriteSize(m_bufferCount);
pendingMutations = m_mutator;
buffer = m_buffer;
m_mutator = new MutatorImpl(m_keyspace, m_rowKeySerializer);
m_buffer = new ArrayList>();
m_bufferCount = 0;
WriteDataJob writeDataJob = new WriteDataJob(pendingMutations, buffer);
//submit job
m_executorService.submit(writeDataJob);
writeDataJob.waitTillStarted();
}
@Override
public void run()
{
while (!m_exit)
{
try
{
Thread.sleep(m_writeDelay);
}
catch (InterruptedException ignored) {}
Mutator pendingMutations = null;
List> buffer = null;
if (m_bufferCount != 0)
{
m_mutatorLock.lock();
try
{
/*m_writeStats.saveWriteSize(m_bufferCount);
pendingMutations = m_mutator;
buffer = m_buffer;
m_mutator = new MutatorImpl(m_keyspace, m_rowKeySerializer);
m_buffer = new ArrayList>();
m_bufferCount = 0;
m_lockCondition.signalAll();*/
submitJob();
}
finally
{
m_mutatorLock.unlock();
}
}
/*try
{
if (pendingMutations != null)
{
for (Tripple data : buffer)
{
pendingMutations.addInsertion(
data.getFirst(),
m_cfName,
new HColumnImpl(data.getSecond(), data.getThird(), data.getTime(), m_columnKeySerializer, m_valueSerializer)
);
}
pendingMutations.execute();
}
pendingMutations = null;
}
catch (Exception e)
{
logger.error("Error sending data to Cassandra ("+m_cfName+")", e);
m_maxBufferSize = m_maxBufferSize * 3 / 4;
logger.error("Reducing write buffer size to "+m_maxBufferSize+
". You need to increase your cassandra capacity or change the kairosdb.datastore.cassandra.write_buffer_max_size property.");
}
//If the batch failed we will retry it without changing the buffer size.
while (pendingMutations != null)
{
try
{
Thread.sleep(100);
}
catch (InterruptedException ignored){ }
try
{
pendingMutations.execute();
pendingMutations = null;
}
catch (Exception e)
{
logger.error("Error resending data", e);
}
}*/
}
}
private class WriteDataJob implements Runnable
{
private Object m_jobLock = new Object();
private boolean m_started = false;
private Mutator m_pendingMutations;
private final List> m_buffer;
public WriteDataJob(Mutator pendingMutations, List> buffer)
{
m_pendingMutations = pendingMutations;
m_buffer = buffer;
}
public void waitTillStarted()
{
synchronized (m_jobLock)
{
while (!m_started)
{
try
{
m_jobLock.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
@Override
public void run()
{
synchronized (m_jobLock)
{
m_started = true;
m_jobLock.notifyAll();
}
/*if (true)
return;*/
try
{
if (m_pendingMutations != null)
{
for (Triple data : m_buffer)
{
HColumnImpl col =
new HColumnImpl(data.getSecond(), data.getThird(), data.getTime(), m_columnKeySerializer, m_valueSerializer);
//if a TTL is set apply it to the column. This will
//cause it to be removed after this number of seconds
if (data.getTtl() != 0)
{
col.setTtl(data.getTtl());
}
m_pendingMutations.addInsertion(
data.getFirst(),
m_cfName,
col
);
}
m_pendingMutations.execute();
}
m_pendingMutations = null;
}
catch (Exception e)
{
logger.error("Error sending data to Cassandra ("+m_cfName+")", e);
m_maxBufferSize = m_maxBufferSize * 3 / 4;
logger.error("Reducing write buffer size to "+m_maxBufferSize+
". You need to increase your cassandra capacity or change the kairosdb.datastore.cassandra.write_buffer_max_size property.");
}
//If the batch failed we will retry it without changing the buffer size.
while (m_pendingMutations != null)
{
try
{
Thread.sleep(100);
}
catch (InterruptedException ignored){ }
try
{
m_pendingMutations.execute();
m_pendingMutations = null;
}
catch (Exception e)
{
logger.error("Error resending data", e);
}
}
}
}
}