Performance Cassandra:如何使用CQL插入性能良好的新宽行
我在评估卡桑德拉。我正在使用datastax驱动程序和CQL 我想用以下内部结构存储一些数据,其中每个更新的名称都不同Performance Cassandra:如何使用CQL插入性能良好的新宽行,performance,cassandra,cql3,datastax-java-driver,Performance,Cassandra,Cql3,Datastax Java Driver,我在评估卡桑德拉。我正在使用datastax驱动程序和CQL 我想用以下内部结构存储一些数据,其中每个更新的名称都不同 +-------+-------+-------+-------+-------+-------+ | | name1 | name2 | name3 | ... | nameN | | time +-------+-------+-------+-------+-------+ | | val1 | val2 | val3 | ...
+-------+-------+-------+-------+-------+-------+
| | name1 | name2 | name3 | ... | nameN |
| time +-------+-------+-------+-------+-------+
| | val1 | val2 | val3 | ... | valN |
+-------+-------+-------+-------|-------+-------+
所以时间应该是列键,名称应该是行键。我用于创建此表的CQL语句是:
CREATE TABLE IF NOT EXISTS test.wide (
time varchar,
name varchar,
value varchar,
PRIMARY KEY (time,name))
WITH COMPACT STORAGE
我希望模式采用这种方式以便于查询。我还必须偶尔存储超过65000行的更新。因此,使用cassandra list/set/map数据类型不是一个选项
我必须能够每秒处理至少1000个宽行插入,并且名称/值对的数量不一但很大(~1000)
问题在于:我编写了一个简单的基准测试,它可以插入1000个宽行,每个行插入10000个名称/值对。CQL和datastax驱动程序的性能非常慢,而不使用CQL的版本(使用astyanax)在同一测试集群上具有良好的性能
我已经读过这篇文章,在这个问题的公认答案中,我建议您应该能够通过使用成批准备的语句(可在cassandra 2中获得)以原子方式快速创建一个新的宽行
所以我试着使用它们,但性能仍然很慢(对于在本地主机上运行的小型三节点集群,每秒插入两次)。我是否遗漏了一些明显的东西,或者我必须使用较低级别的thrift API我在astyanax中使用ColumnListVariation实现了相同的插入,每秒大约有30个插入。
如果我必须使用较低级别的thrift API:
- 它实际上是不推荐使用的,还是因为级别较低而不方便使用
- 我能用CQL查询用thrift api创建的表吗
packassandra
导入com.datastax.driver.core_
对象CassandRatesMinimized扩展应用程序{
val keyspace=“测试”
val table=“宽”
val tableName=s“$keyspace.$table”
def createKeyspace=s”“”
如果不存在,则创建键空间${KEYSPACE}
使用REPLICATION={'class':'SimpleStrategy','REPLICATION\u factor':1}
"""
def createWideTable=s”“”
创建表(如果不存在)${tableName}(
时间瓦查尔,
名字叫瓦查尔,
varchar值,
主键(时间、名称))
具有紧凑的存储空间
"""
def writeTimeNameValue(时间:字符串)=s“”
插入到${tableName}(时间、名称、值)
值(“$time”、?、?)
"""
val cluster=cluster.builder.addContactPoints(“127.0.0.1”).build
val session=cluster.connect()
执行(createKeyspace)
执行(createWideTable)
对于(我一些您可以尝试的东西…在您的cassandra.yaml
(这是cassandra 1.2.x,可能在2.x中对参数的调用有所不同):
- 禁用行缓存(
行缓存大小(单位:0
)
- 在内存行溢出到磁盘之前增加内存限制(
min\u memory\u compression\u limit\u in\u mb
),仅当您看到一些日志输出表明溢出确实发生时才执行此操作
- 确保正确配置了
num\u令牌
/初始令牌
值,以便在节点上分布行
其他你可以尝试的事情:
- 向客户端提供集群中的所有节点IP,而不仅仅是一个
- 为每个Cassandra节点提供更多RAM
- 尝试运行多线程测试
- 如果您运行Cassandra,请确保您拥有并正在使用
在Linux上
需要澄清的事项:
- 您是否已通过
nodetool
确认这3个节点已找到每个节点
其他的
nodetool
对3个节点的负载分布有何说明
- 虚拟集群的物理主机对CPU和
I/O使用情况?可能只是达到了最大值
您的代码中有一个错误,我认为它解释了您看到的许多性能问题:对于每个批处理,您都要重新准备语句。准备语句并不是非常昂贵,但这样做会增加很多延迟。等待语句准备的时间是您不构建批处理的时间,并且Cassandra不花时间处理该批处理。一个准备好的语句只需要准备一次,应该重复使用
我认为性能差的大部分原因可以解释为延迟问题。瓶颈很可能是你的应用程序代码,而不是Cassandra。即使你只准备了一次声明,你仍然会花大部分时间在应用程序中,要么是CPU受限(构建一个大批量),要么什么都不做(等待网络和Cassandra)
您可以做两件事:首先,使用CQL驱动程序的异步API并在网络和Cassandra忙于您刚刚完成的那一批时构建下一批;其次,尝试运行多个线程来做同样的事情。您必须测试的线程的确切数量取决于您所使用的内核数量如果您在同一台计算机上运行一个或三个节点,则具有和
在同一台计算机上运行三节点群集会使群集比运行单个节点慢,而在不同的计算机上运行会使群集更快。此外,在同一台计算机上运行应用程序也没有什么帮助。如果要测试性能,请只运行一个节点,或者在不同的计算机上运行真正的群集
批处理可以为您提供额外的性能,但并不总是这样。它们可以
package cassandra
import com.datastax.driver.core._
object CassandraTestMinimized extends App {
val keyspace = "test"
val table = "wide"
val tableName = s"$keyspace.$table"
def createKeyspace = s"""
CREATE KEYSPACE IF NOT EXISTS ${keyspace}
WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }
"""
def createWideTable = s"""
CREATE TABLE IF NOT EXISTS ${tableName} (
time varchar,
name varchar,
value varchar,
PRIMARY KEY (time,name))
WITH COMPACT STORAGE
"""
def writeTimeNameValue(time: String) = s"""
INSERT INTO ${tableName} (time, name, value)
VALUES ('$time', ?, ?)
"""
val cluster = Cluster.builder.addContactPoints("127.0.0.1").build
val session = cluster.connect()
session.execute(createKeyspace)
session.execute(createWideTable)
for(i<-0 until 1000) {
val entries =
for {
i <- 0 until 10000
name = i.toString
value = name
} yield name -> value
val batchPreparedStatement = writeMap(i, entries)
val t0 = System.nanoTime()
session.execute(batchPreparedStatement)
val dt = System.nanoTime() - t0
println(i + " " + (dt/1.0e9))
}
def writeMap(time: Long, update: Seq[(String, String)]) : BatchStatement = {
val template = session
.prepare(writeTimeNameValue(time.toString))
.setConsistencyLevel(ConsistencyLevel.ONE)
val batch = new BatchStatement(BatchStatement.Type.UNLOGGED)
for ((k, v) <- update)
batch.add(template.bind(k, v))
batch
}
}
package cassandra;
import java.util.Iterator;
import com.netflix.astyanax.ColumnListMutation;
import com.netflix.astyanax.serializers.AsciiSerializer;
import com.netflix.astyanax.serializers.LongSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.astyanax.AstyanaxContext;
import com.netflix.astyanax.Keyspace;
import com.netflix.astyanax.MutationBatch;
import com.netflix.astyanax.connectionpool.NodeDiscoveryType;
import com.netflix.astyanax.connectionpool.OperationResult;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.connectionpool.impl.ConnectionPoolConfigurationImpl;
import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor;
import com.netflix.astyanax.impl.AstyanaxConfigurationImpl;
import com.netflix.astyanax.model.Column;
import com.netflix.astyanax.model.ColumnFamily;
import com.netflix.astyanax.model.ColumnList;
import com.netflix.astyanax.thrift.ThriftFamilyFactory;
public class AstClient {
private static final Logger logger = LoggerFactory.getLogger(AstClient.class);
private AstyanaxContext<Keyspace> context;
private Keyspace keyspace;
private ColumnFamily<Long, String> EMP_CF;
private static final String EMP_CF_NAME = "employees2";
public void init() {
logger.debug("init()");
context = new AstyanaxContext.Builder()
.forCluster("Test Cluster")
.forKeyspace("test1")
.withAstyanaxConfiguration(new AstyanaxConfigurationImpl()
.setDiscoveryType(NodeDiscoveryType.RING_DESCRIBE)
)
.withConnectionPoolConfiguration(new ConnectionPoolConfigurationImpl("MyConnectionPool")
.setPort(9160)
.setMaxConnsPerHost(1)
.setSeeds("127.0.0.1:9160")
)
.withAstyanaxConfiguration(new AstyanaxConfigurationImpl()
.setCqlVersion("3.0.0")
.setTargetCassandraVersion("2.0.5"))
.withConnectionPoolMonitor(new CountingConnectionPoolMonitor())
.buildKeyspace(ThriftFamilyFactory.getInstance());
context.start();
keyspace = context.getClient();
EMP_CF = ColumnFamily.newColumnFamily(
EMP_CF_NAME,
LongSerializer.get(),
AsciiSerializer.get());
}
public void insert(long time) {
MutationBatch m = keyspace.prepareMutationBatch();
ColumnListMutation<String> x =
m.withRow(EMP_CF, time);
for(int i=0;i<10000;i++)
x.putColumn(Integer.toString(i), Integer.toString(i));
try {
@SuppressWarnings("unused")
Object result = m.execute();
} catch (ConnectionException e) {
logger.error("failed to write data to C*", e);
throw new RuntimeException("failed to write data to C*", e);
}
logger.debug("insert ok");
}
public void createCF() {
}
public void read(long time) {
OperationResult<ColumnList<String>> result;
try {
result = keyspace.prepareQuery(EMP_CF)
.getKey(time)
.execute();
ColumnList<String> cols = result.getResult();
// process data
// a) iterate over columsn
for (Iterator<Column<String>> i = cols.iterator(); i.hasNext(); ) {
Column<String> c = i.next();
String v = c.getStringValue();
System.out.println(c.getName() + " " + v);
}
} catch (ConnectionException e) {
logger.error("failed to read from C*", e);
throw new RuntimeException("failed to read from C*", e);
}
}
public static void main(String[] args) {
AstClient c = new AstClient();
c.init();
long t00 = System.nanoTime();
for(int i=0;i<1000;i++) {
long t0 = System.nanoTime();
c.insert(i);
long dt = System.nanoTime() - t0;
System.out.println((1.0e9/dt) + " " + i);
}
long dtt = System.nanoTime() - t00;
c.read(0);
System.out.println(dtt / 1e9);
}
}