I have a list of unordered events and my task is to store first and last occurrences for them.
I have following column family in Cassandra:
CREATE TABLE events (
event_name TEXT,
first_occurrence BIGINT,
last_occurrence BIGINT,
PRIMARY KEY (event_name)
);
So if I have an event with the name "some_event" and occurrence with 123456, what I want to do is something which in MySQL terms would look like this:
INSERT INTO events (event_name, first_occurence, last_occurence)
VALUES ('some_event', 123456, 123456)
ON DUPLICATE KEY UPDATE
first_occurrence = LEAST(first_occurrence, 12345),
last_occurrence = GREATEST(last_occurrence, 123456)
I was going to use lightweight transactions in Cassandra to accomplish it, like this:
INSERT INTO events(event_name, first_occurrence, last_occurrence) VALUES ('some_event', 12345, 12345) IF NOT EXISTS;
UPDATE events SET first_occurrence = 123456 WHERE event_name='some_event' IF first_occurrence > 123456;
UPDATE events SET last_occurrence = 123456 WHERE event_name='some_event' IF last_occurrence < 123456;
But as it turns out, CQL3 does not allow < and > operators in lightweight transactions IF clause.
So my question is, what is the pattern for doing such conditional updates?
What version of cassandra are you running? Support for non-equal conditions with LWTs was added in 2.1.1 via CASSANDRA-6839:
cqlsh:test> UPDATE events SET first_occurrence = 123456 WHERE event_name='some_event' IF first_occurrence > 1;
[applied]
-----------
True
Cassandra does not do read before write with only 2 exceptions - counters and "lightweight transactions". As a result you will not be able to reliably implement your scenario directly in Cassandra. Even if you read the values out and then make an update based on these values, you may overwrite someone else's changes, since there is no locking and isolation in Cassandra, and eventual consistency makes it even worse.
So if you need to implement something like this, you will need to do it outside of Cassandra. Create a synchronization layer which will provide a central point for Cassandra writes, and make this layer responsible for the logic. Just make sure that no writes are making it around this layer.
Related
I have a table in Cassandra with 2 columns: id and date_proc and plan to insert a lot of inserts. Is it possible to use something like ON CONFLICT in Postgres to get previous value on inserting?
Could you tell me another way to avoid 2 requests to Cassandra (select and insert)? Maybe some solution in DataStax?
ddl:
create table test.date_dict (
id text,
date_proc text,
PRIMARY KEY (id));
example of inserting:
INSERT INTO test.date_dict (id, date_proc) VALUES ('1', '2020-01-01'); // return '2020-01-01'
INSERT INTO test.date_dict (id, date_proc) VALUES ('1', '2020-01-05'); // return '2020-01-01'
"Normal" inserts and updates in Cassandra are just appends into the memtable (and then flushed into SSTables) - no read happens during these operations. And it will just overwrite previous data if it has lower timestamp.
Potentially you can use lightweight transactions (LWTs) to achieve what you need - they return previous value if there is a conflict (row exists already when you use IF NOT EXISTS, or value is different than you specify in the IF condition). But LWTs are very bad for performance, so they should be used carefully.
I would try to reformulate your task such way so it will fit into "normal" inserts/updates behavior.
I've read at least 50 articles on this and still don't know the answer ...
I know how partitioning, clustering and ALLOW FILTERING work, but can't figure out what is the situation behind using ALLOW FILTERING with all partition keys provided in a query.
I have a table like this:
CREATE TABLE IF NOT EXISTS keyspace.events (
date_string varchar,
starting_timestamp bigint,
event_name varchar,
sport_id varchar
PRIMARY KEY ((date_string), starting_timestamp, id)
);
How does query like this work ?
SELECT * FROM keyspace.events
WHERE
date_string IN ('', '', '') AND
starting_timestamp < '' AND
sport_id = 1 /* not in partitioning nor clustering key */
ALLOW FILTERING;
Is the 'sport_id' filtering done on records retreived earlier by the correctly defined keys ? Is ALLOW FILTERING still discouraged in this kind of query ?
How should I perform filtering in this particular situation ?
Thanks in advance
Yes, it should first filter out the partitions and then only will do the filtering on the non-key value and as per the experiment mentioned here : https://dzone.com/articles/apache-cassandra-and-allow-filtering
I think its safe to use the allow filtering after all the keys in most case.
It will highly depend on how much data you are filtering out as well - if the last condition of sport_id = 1 is trying to filter out most of the data then it will be a bad idea as it gives a lot of pressure to the database, so you need to consider the trade-offs here.
Its not a good idea to use an IN clause with the partition key - especially the above query doesnt look good because its using both IN clause on Partition key and the allow filtering.
Suggestion - Cassandra is very good at processing as many requests as you need in a second and the design idea should be to send more lighter queries at once than trying to send one query which does lot of work. So my suggestion would be to fire N calls to Cassandra each with = condition on partition key without filtering the last column and then combine and do final filter in the code (which ever language you are using I assume it can support sending all these calls parallel to the database). By doing so you will get the advantage in performance in long term when the data grows.
So, I have a Cassandra CQL statement that looks like this:
SELECT * FROM DATA WHERE APPLICATION_ID = ? AND PARTNER_ID = ? AND LOCATION_ID = ? AND DEVICE_ID = ? AND DATA_SCHEMA = ?
This table is sorted by a timestamp column.
The functionality is fronted by a REST API, and one of the filter parameters that they can specify to get the most recent row, and then I appent "LIMIT 1" to the end of the CQL statement since it's ordered by the timestamp column in descending order. What I would like to do is allow them to specify multiple device id's to get back the latest entries for. So, my question is, is there any way to do something like this in Cassandra:
SELECT * FROM DATA WHERE APPLICATION_ID = ? AND PARTNER_ID = ? AND LOCATION_ID = ? AND DEVICE_ID IN ? AND DATA_SCHEMA = ?
and still use something like "LIMIT 1" to only get back the latest row for each device id? Or, will I simply have to execute a separate CQL statement for each device to get the latest row for each of them?
FWIW, the table's composite key looks like this:
PRIMARY KEY ((application_id, partner_id, location_id, device_id, data_schema), activity_timestamp)
) WITH CLUSTERING ORDER BY (activity_timestamp DESC);
IN is not recommended when there are a lot of parameters for it and under the hood it's making reqs to multiple partitions anyway and it's putting pressure on the coordinator node.
Not that you can't do it. It is perfectly legal, but most of the time it's not performant and is not suggested. If you specify limit, it's for the whole statement, basically you can't pick just the first item out from partitions. The simplest option would be to issue multiple queries to the cluster (every element in IN would become one query) and put a limit 1 to every one of them.
To be honest this was my solution in a lot of the projects and it works pretty much fine. Basically coordinator would under the hood go to multiple nodes anyway but would also have to work more for you to get you all the requests, might run into timeouts etc.
In short it's far better for the cluster and more performant if client asks multiple times (using multiple coordinators with smaller requests) than to make single coordinator do to all the work.
This is all in case you can't afford more disk space for your cluster
Usual Cassandra solution
Data in cassandra is suggested to be ready for query (query first). So basically you would have to have one additional table that would have the same partitioning key as you have it now, and you would have to drop the clustering column activity_timestamp. i.e.
PRIMARY KEY ((application_id, partner_id, location_id, device_id, data_schema))
double (()) is intentional.
Every time you would write to your table you would also write data to the latest_entry (table without activity_timestamp) Then you can specify the query that you need with in and this table contains the latest entry so you don't have to use the limit 1 because there is only one entry per partitioning key ... that would be the usual solution in cassandra.
If you are afraid of the additional writes, don't worry , they are inexpensive and cpu bound. With cassandra it's always "bring on the writes" I guess :)
Basically it's up to you:
multiple queries - a bit of refactoring, no additional space cost
new schema - additional inserts when writing, additional space cost
Your table definition is not suitable for such use of the IN clause. Indeed, it is supported on the last field of the primary key or the last field of the clustering key. So you can:
swap your two last fields of the primary key
use one query for each device id
I have a table - for simplicity, lets say this is its definition:
CREATE TABLE t (pk1 varchar, pk2 varchar, c1 varchar, c2 varchar, PRIMARY KEY(pk1, pk2));
I do multiple actions on it in parallel using the full PK:
INSERT INTO t (pk1, pk2, c1, c2) values (?, ?, ?, ?) IF NOT EXISTS;
DELETE FROM t where pk1 = ? AND pk2 = ?;
UPDATE t set c1 = ? where pk1 = ? AND pk2 = ? IF EXISTS;
Note:
in the INSERT command c2 is never null
in the UPDATE command c2 is not populated
Using these commands I should never have rows with c2 = null. The problem is that every now and then I do see such rows. I can't easily reproduce it but it always happens when I stress the system (multiple parallel clients running: insert, update, delete with the same PK).
Edit: my cluster size is 4 with RF=2 (NetworkTopologyStrategy with 1 DC) and I use CL=QUORUM for all queries.
Am I missing something or is there a bug in LWT?
https://docs.datastax.com/en/cassandra/3.0/cassandra/dml/dmlLtwtTransactions.html
If lightweight transactions are used to write to a row within a partition,only lightweight transactions for both read and write operations should be used. This caution applies to all operations, whether individual or batched. For example, the following series of operations can fail:
DELETE ...
INSERT .... IF NOT EXISTS
SELECT ....
The following series of operations will work:
DELETE ... IF EXISTS
INSERT .... IF NOT EXISTS
SELECT .....
Note - The same is true for INSERT and UPDATE combination as well from a bug we recently encountered. If you use Transactions then use it for the related statements. The recent could be related to the slightly different timestamps, explained here better
https://jira.apache.org/jira/browse/CASSANDRA-14304
doanduyhai DOAN DuyHai added a comment - 24/Mar/18 15:12
Hints:
1) LWT operations are using the ballot based on an agreement of timestamp value between QUORUM of replicas. It can happens that the timestamp is slightly incremented (some microsecs) in case of conflict/contention on the cluster. The consequence is that the timestamp used for LWT can be slightly (again in microsecs) in the future. Read the source code to check the part of the code responsible for ballot agreement with Paxos
2) For the DELETE request:
a) it can use the <current> timestamp, which can belong to the "past" with respect to the one used by LWT, thus SELECT does return a value
In general one option is that the Delete is executed in parallel with the Update yet does not provide any guarantee of being applied.
Example (this is for simplicity - it may be possible in other options as well).
Assume a cluster of 3 nodes with RF=3 (that has a temporary connection issue with node1 and the other node2,node3)
The Delete with CL=ONE is executed toward node1 with timestamp T1 (and not applied on node2, node3).
The Updates is executed toward node2,node3 with timestamp T2 (T2 > T1).
Connection between node1,node2,node3 is restored and now the tombstone that DELETE introduced would remove all the data (including c2) and the UPDATE would only have set pk1,pk2,c1 - leaving c2 as null.
If you would apply the DELETE using LWT - this should not happen as long as TTL is not used.
TTL can be set either directly in the insert statements or by default via a table property, to check this you can execute
Describe table will return the default_time_to_live that is set for this table.
A select ttl(c2) ... will return a value if ttl was set.
I have a Cassandra table as below:
create table experience.userstats (stream varchar, user varchar, computer varchar, experience varchar, amount int, primary key (experience, stream, user, computer);
I am using Apache storm bolts to create these records in Cassandra from a queue spout.
I want to generate aggregate statistics and display the counter for different experiences for a given stream as a pie chart. For example, 30% satisfied, 40% mixed and 30% unsatisfied experience.
Since I cannot update counters online, I am using a Clock spout (timer) to read this data and generate counters into a new table. But Cassandra does not support group by clauses and hence I have to read thrice to get the counter for each experience as below.
select count(*) from experience.userstats where experience='satisfied' and stream='xyz';
select count(*) from experience.userstats where experience='unsatisfied' and stream='xyz';
select count(*) from experience.userstats where experience='mixed' and stream='xyz';
The problem with this approach is that I have to execute three queries on database to get the counters for the 3 types of experience and experience could be modified in the mean-time making these counters inconsistent.
Is there a way I can execute above three select statements in a single batch? Or could you please suggest an alternative design approach?
you can use
IN
relation operator
like this,
SELECT count(*) FROM userstats WHERE experience IN('mixed','unsatisfied','satisfied) AND stream = 'xyz' ;
Executing count(*) will give you timeouts easy. Why don't you use counter field and just increment it when insert happens? This way you will just query for one value. Distributed counters are improved in 2.1 and later and they work great.