Netflix / astyanax

Cassandra Java Client
Apache License 2.0
1.04k stars 354 forks source link

MutationBatch failing to insert rows #572

Open ffarfan opened 9 years ago

ffarfan commented 9 years ago

While trying to apply a batch of inserts using MutationBatch, we get the following exception:

com.netflix.astyanax.connectionpool.exceptions.BadRequestException: BadRequestException: [host=XXX.XXX.XXX.XXX(XXX.XXX.XXX.XXX):9160, latency=63(138), attempts=1]InvalidRequestException(why:Not enough bytes to read value of component 0)
    at com.netflix.astyanax.thrift.ThriftConverter.ToConnectionPoolException(ThriftConverter.java:159)
    at com.netflix.astyanax.thrift.AbstractOperationImpl.execute(AbstractOperationImpl.java:65)
    at com.netflix.astyanax.thrift.AbstractOperationImpl.execute(AbstractOperationImpl.java:28)
    at com.netflix.astyanax.thrift.ThriftSyncConnectionFactoryImpl$ThriftConnection.execute(ThriftSyncConnectionFactoryImpl.java:151)
    at com.netflix.astyanax.connectionpool.impl.AbstractExecuteWithFailoverImpl.tryOperation(AbstractExecuteWithFailoverImpl.java:119)
    at com.netflix.astyanax.connectionpool.impl.AbstractHostPartitionConnectionPool.executeWithFailover(AbstractHostPartitionConnectionPool.java:338)
    at com.netflix.astyanax.thrift.ThriftKeyspaceImpl.executeOperation(ThriftKeyspaceImpl.java:517)
    at com.netflix.astyanax.thrift.ThriftKeyspaceImpl.access$000(ThriftKeyspaceImpl.java:93)
    at com.netflix.astyanax.thrift.ThriftKeyspaceImpl$1.execute(ThriftKeyspaceImpl.java:137)
    at DataImporter.applyBatch(DataImporter.java:88)
    at DataImporter.main(DataImporter.java:40)
Caused by: InvalidRequestException(why:Not enough bytes to read value of component 0)
    at org.apache.cassandra.thrift.Cassandra$batch_mutate_result.read(Cassandra.java:20833)
    at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:78)
    at org.apache.cassandra.thrift.Cassandra$Client.recv_batch_mutate(Cassandra.java:964)
    at org.apache.cassandra.thrift.Cassandra$Client.batch_mutate(Cassandra.java:950)
    at com.netflix.astyanax.thrift.ThriftKeyspaceImpl$1$1.internalExecute(ThriftKeyspaceImpl.java:150)
    at com.netflix.astyanax.thrift.ThriftKeyspaceImpl$1$1.internalExecute(ThriftKeyspaceImpl.java:141)
    at com.netflix.astyanax.thrift.AbstractOperationImpl.execute(AbstractOperationImpl.java:60)
    ... 9 more

This is the definition of our test table created for this minimal example:

CREATE TABLE test.my_table (
  id timeuuid,
  version_number int,
  content blob,
  PRIMARY KEY ((id))
);

This is the code we use to define our context and column family:

context = new AstyanaxContext.Builder()
    .forCluster("Test Cluster")
    .forKeyspace("test")
    .withAstyanaxConfiguration(astyanaxConfigurationImplDiscovery)
    .withConnectionPoolConfiguration(connectionPoolConfigurationImpl)
    .withAstyanaxConfiguration(astyanaxConfigurationImplVersioning)
    .withConnectionPoolMonitor(new CountingConnectionPoolMonitor())
    .buildKeyspace(ThriftFamilyFactory.getInstance());

context.start();

columnFamily = ColumnFamily.newColumnFamily(
    TABLE_NAME,
    TimeUUIDSerializer.get(),
    StringSerializer.get()
);

and the tiny method that we have to add rows to the MutationBatch:

private static void importData(UUID uuid, int versionNumber, String content)
    throws ConnectionException
{
    mutationBatch.withRow(columnFamily, uuid)
        .putColumn("version_number", versionNumber)
        .putColumn("content", content);
}

After all this, our application throws the exception when we execute the batch.

We can work around the issue by using ColumnFamilyQuery.useCql instead, or stick with MutationBatch but recreate the table "WITH COMPACT STORAGE."

zznate commented 9 years ago

@ffarfan Not really a bug - this is by design. Your last sentence sums up your two options:

There is a third options, but it moves you into having to pack the composites by hand via annotations. This blog we wrote is a bit dated, but describes what is going on in detail: http://thelastpickle.com/blog/2013/09/13/CQL3-to-Astyanax-Compatibility.html

ffarfan commented 9 years ago

@zznate thanks for the prompt reply. I'll take at the blog post you shared.

Kurt-von-Laven commented 9 years ago

@zznate, so if I understood correctly, the rationale for the exception being thrown is to discourage batch inserts in CQL3 because they are often a performance bottleneck?

zznate commented 9 years ago

@Kurt-von-Laven not so much to discourage. It has to do with how CQL table typing works under the hood - specifically the use composite prefixes as column meta data. Thrift can read composite columns just fine, they just need to be extracted by hand when doing so. That particular exception is Thrift complaining because it hit a composite column prefix when it was expecting the column to be a single byte stream as it does not have the concept of the meta-data for parsing such out.

That said...

If what you are looking to do is constant-sized batch inserts (like event streams/time series stuff particularly where you pop a consistent number of messages off a queue for insertion) Thrift will be significantly more performant and COMPACT STORAGE will save storage overhead by removing the composite prefixing. Indeed, this is a use case that brought a lot of us to Cassandra in the first place.

Maintaining a standard CQL pool for day-to-day CRUD and a small pool of Thrift connections for large, shaped batch inserts (or wide reads for that matter) is a perfectly legitimate setup.