18. CQL null semantics!
@doanduyhai
18
Reading null value means
• value does not exist (has never bean created)
• value deleted (tombstone)
SELECT age FROM users WHERE login = ddoan; à NULL
19. CQL null semantics!
@doanduyhai
19
Writing null means
• delete value (creating tombstone)
• even though it does not exist
UPDATE users SET age = NULL WHERE login = ddoan;
20. CQL null semantics!
@doanduyhai
20
Seen in production: prepared statement
UPDATE users SET
age = ?,
…
geo_location = ?,
mood = ?,
…
WHERE login = ?;
21. CQL null semantics!
@doanduyhai
21
Seen in production: bound statement
preparedStatement.bind(33, …, null, null, null, …);
null ☞ tombstone creation on each update …
jdoe
age name geo_loc mood status
33 John DOE ý ý ý
23. Intensive update!
@doanduyhai
23
Context
• small start-up
• cloud-based video recording & alarm
• internet of things (sensor)
• 10 updates/sec for some sensors
24. Intensive update on same column!
@doanduyhai
24
Data model
sensor_id
value
45.0034
CREATE TABLE sensor_data (
sensor_id long,
value double,
PRIMARY KEY(sensor_id));
25. Intensive update on same column!
UPDATE sensor_data SET value = 45.0034 WHERE sensor_id = …;
UPDATE sensor_data SET value = 47.4182 WHERE sensor_id = …;
UPDATE sensor_data SET value = 48.0300 WHERE sensor_id = …;
@doanduyhai
25
Updates
sensor_id
value (t1)
45.0034
sensor_id
value (t13)
47.4182
sensor_id
value (t36)
48.0300
26. Intensive update on same column!
@doanduyhai
26
Read
SELECT sensor_value from sensor_data WHERE sensor_id = …;
read N physical columns, only 1 useful …
sensor_id
value (t1)
45.0034
sensor_id
value (t13)
47.4182
sensor_id
value (t36)
48.0300
27. Intensive update on same column!
@doanduyhai
27
Solution 1: leveled compaction! (if your I/O can keep up)
sensor_id
value (t1)
45.0034
sensor_id
value (t13)
47.4182
sensor_id
value (t36)
48.0300
sensor_id
value (t36)
48.0300
28. Intensive update on same column!
@doanduyhai
28
Solution 2: reversed timeseries & DateTiered compaction strategy
CREATE TABLE sensor_data (
sensor_id long,
date timestamp,
sensor_value double,
PRIMARY KEY((sensor_id), date))
WITH CLUSTERING ORDER (date DESC);
29. Intensive update on same column!
SELECT sensor_value FROM sensor_data WHERE sensor_id = … LIMIT 1;
@doanduyhai
29
sensor_id
date3(t3)
date2(t2)
date1(t1)
Data cleaning by configuration (max_sstable_age_days)
...
48.0300 47.4182 45.0034 …
31. Design around dynamic schema!
@doanduyhai
31
Customer emergency call
• 3 nodes cluster almost full
• impossible to scale out
• 4th node in JOINING state for 1 week
• disk space is filling up, production at risk!
32. Design around dynamic schema!
@doanduyhai
32
After investigation
• 4th node in JOINING state because streaming is stalled
• NPE in logs
33. Design around dynamic schema!
@doanduyhai
33
After investigation
• 4th node in JOINING state because streaming is stalled
• NPE in logs
Cassandra source-code to the rescue
34. Design around dynamic schema!
@doanduyhai
34
public class CompressedStreamReader extends StreamReader
{
…
@Override
public SSTableWriter read(ReadableByteChannel channel) throws IOException
{
…
Pair<String, String> kscf = Schema.instance.getCF(cfId);
ColumnFamilyStore cfs = Keyspace.open(kscf.left).getColumnFamilyStore(kscf.right);
NPE here
35. Design around dynamic schema!
@doanduyhai
35
The truth is
• the devs dynamically drop & recreate table every day
• dynamic schema is in the core of their design
Example:
DROP TABLE catalog_127_20140613;
CREATE TABLE catalog_127_20140614( … );
39. Design around dynamic schema!
@doanduyhai
39
Consequences
• joining node got always stuck
• à cannot extend cluster
•
à changing code takes time
•
à production in danger (no space left)
•
à sacrify analytics data to survive
40. Design around dynamic schema!
@doanduyhai
40
Nutshell
• dynamic schema change as normal operations is not recommended
• concurrent schema AND topology change is an anti-pattern
53. Rate limiting!
@doanduyhai
53
Solution
• premium phone number ☞ Google libphonenumber
• massive hack ☞ rate limiting with Cassandra
54. Cassandra Time To Live!
@doanduyhai
54
Time to live
• built-in feature
• insert data with a TTL in sec
• expires server-side automatically
• ☞ use as sliding-window
55. Rate limiting in action!
@doanduyhai
55
Implementation
• threshold = max 3 reset password per sliding 24h
56. Rate limiting in action!
@doanduyhai
56
Implementation
• when /password/reset called
• check threshold
• reached ☞ error message/ignore
• not reached ☞ log the attempt with TTL = 86400
58. Anti Fraud!
@doanduyhai
58
Real story
• many special offers available
• 30 mins international calls (50 countries)
• unlimited land-line calls to 5 countries
• …
59. Anti Fraud!
@doanduyhai
59
Real story
• each offer has a duration (week/month/year)
• only one offer active at a time
60. Anti Fraud!
@doanduyhai
60
Cassandra TTL
• check for existing offer before
SELECT count(*) FROM user_special_offer WHERE login = ‘jdoe’;
61. Anti Fraud!
@doanduyhai
61
Cassandra TTL
• then grant new offer
INSERT INTO user_special_offer(login, offer_code, …)
VALUES(‘jdoe’, ’30_mins_international’,…)
USING TTL <offer_duration>;
62. Account Validation!
@doanduyhai
62
Requirement
• user creates new account
• sends sms/email link with token to validate account
• 10 days to validate
63. Account Validation!
@doanduyhai
63
How to ?
• create account with 10 days TTL
INSERT INTO users(login, name, age)
VALUES(‘jdoe’, ‘John DOE’, 33)
USING TTL 864000;
64. Account Validation!
@doanduyhai
64
How to ?
• create random token for validation with 10 days TTL
INSERT INTO account_validation(token, login, name, age)
VALUES(‘A0F83E63DB935465CE73DFE…’, ‘jdoe’, ‘John DOE’, 33)
USING TTL 864000;
65. Account Validation!
@doanduyhai
65
On token validation
• check token exist & retrieve user details
SELECT login, name, age FROM account_validation
WHERE token = ‘A0F83E63DB935465CE73DFE…’;
• re-insert durably user details without TTL
INSERT INTO users(login, name, age) VALUES(‘jdoe’, ‘John DOE’, 33);
66. Sensor Data Timeseries!
@doanduyhai
66
Requirements
• lots of sensors (103 – 106)
• medium to high insertion rate (0.1 – 10/secs)
• keep good load balancing
• fast read & write
72. Bucketing!
@doanduyhai
72
But how can I select raw data between 14:45 and 15:10 ?
14:45 à ?
15:00 à 15:10
sensor_id:2014091014
date1 date2 date3 date4 …
blob1 blob2 blob3 blob4 …
sensor_id:2014091015
date11 date12 date13 date14 …
blob11 blob12 blob13 blob14 …
73. Bucketing!
Solution
• use IN clause on partition key component
• with range condition on date column
☞ date column should be monotonic function (increasing/decreasing)
@doanduyhai
73
SELECT * FROM sensor_data WHERE sensor_id = xxx
AND date_bucket IN (2014091014 , 2014091015)
AND date >= ‘2014-09-10 14:45:00.000‘
AND date <= ‘2014-09-10 15:10:00.000‘
74. Bucketing Caveats!
@doanduyhai
74
IN clause for #partition is not silver bullet !
• use scarcely
• keep cardinality low (≤ 5)
n1
n2
n3
n4
n5
n6
n7
coordinator
n8
sensor_id:2014091014
sensor_id:2014091015
75. Bucketing Caveats!
@doanduyhai
75
IN clause for #partition is not silver bullet !
• use scarcely
• keep cardinality low (≤ 5)
• prefer // async queries
• ease of query vs perf
n1
n2
n3
n4
n5
n6
n7
n8
Async client
sensor_id:2014091014
sensor_id:2014091015