Prepared statement caching issue in Cassandra Csha

2019-02-25 07:49发布

I believe I have found a bug with the logic of how a prepared statement is cached in the StatementFactory in the Cassandra csharp driver (version 2.7.3). Here is the use case.

Guid key = Guid.NewGuid(); // your key

ISession session_foo = new Session("foo"); //This is pseudo code
ISession session_bar = new Session("bar");

var foo_mapper = new Mapper(session_foo); //table foo_bar
var bar_mapper = new Mapper(session_bar); //table foo_bar

await Task.WhenAll(
   foo_mapper.DeleteAsync<Foo>("WHERE id = ?", key),
   bar_mapper.DeleteAsync<Bar>("WHERE id = ?", key));

We have found that after running this deletes, only the first request is succeeding. After diving in the the source code of StatementFactory

public Task<Statement> GetStatementAsync(ISession session, Cql cql)
    {
        if (cql.QueryOptions.NoPrepare)
        {
            // Use a SimpleStatement if we're not supposed to prepare
            Statement statement = new SimpleStatement(cql.Statement, cql.Arguments);
            SetStatementProperties(statement, cql);
            return TaskHelper.ToTask(statement);
        }
        return _statementCache
            .GetOrAdd(cql.Statement, session.PrepareAsync)
            .Continue(t =>
            {
                if (_statementCache.Count > MaxPreparedStatementsThreshold)
                {
                    Logger.Warning(String.Format("The prepared statement cache contains {0} queries. Use parameter markers for queries. You can configure this warning threshold using MappingConfiguration.SetMaxStatementPreparedThreshold() method.", _statementCache.Count));
                }
                Statement boundStatement = t.Result.Bind(cql.Arguments);
                SetStatementProperties(boundStatement, cql);
                return boundStatement;
            });
    }

You can see that the cache only uses the cql statement. In our case, we have the same table names in different keyspaces (aka sessions). Our cql statement in both queries look the same. ie DELETE FROM foo_bar WHERE id =?.

If I had to guess, I would say that a simple fix would be to combine the cql statement and keyspace together as the cache key.

Has anyone else run into this issue before?

2条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-02-25 08:14

There is an open ticket to fix this behaviour.

As a workaround, you can use a different MappingConfiguration instances when creating the Mapper:

ISession session1 = cluster.Connect("ks1");
ISession session2 = cluster.Connect("ks2");

IMapper mapper1 = new Mapper(session1, new MappingConfiguration());
IMapper mapper2 = new Mapper(session2, new MappingConfiguration());

Or, you can reuse a single ISession instance and fully-qualify your queries to include the keyspace.

MappingConfiguration.Global.Define(
   new Map<Foo>()
      .TableName("foo")
      .KeyspaceName("ks1"),
   new Map<Bar>()
      .TableName("bar")
      .KeyspaceName("ks2"));

ISession session = cluster.Connect();
IMapper mapper = new Mapper(session);
查看更多
唯我独甜
3楼-- · 2019-02-25 08:20

As a simple workaround, I am skipping the cache by using the DoNotPrepare

await _mapper.DeleteAsync<Foo>(Cql.New("WHERE id = ?", key).WithOptions(opt => opt.DoNotPrepare()));

I also found an open issue with Datastax

查看更多
登录 后发表回答