SlideShare a Scribd company logo
Building Java Applications
Technical Evangelist for Apache Cassandra
@chbatey
@chbatey
Who am I?
• Technical Evangelist for Apache Cassandra
•Founder of Stubbed Cassandra
•Help out Apache Cassandra users
• DataStax
•Builds enterprise ready version of Apache
Cassandra
• Previous: Cassandra backed apps at BSkyB
@chbatey
Agenda
• Everything I wish I knew before I started
• Example application: Auction service
- Build + Deploy
- Tech stack: Spring vs Dropwizard
- Configuration
- Designing schemas
- Example Driver code including LWTs
- Continuous Integration + Testing strategies
@chbatey
The old
@chbatey
Scaling the old
@chbatey
Uh oh :(
@chbatey
Uh oh :(
@chbatey
A pile of cats
@chbatey
The new
@chbatey
Strangulation
@chbatey
Prerequisites
@chbatey
Use the DataStax docs
@chbatey
Say no to thrift
http://exponential.io/blog/2015/01/08/cassandra-terminology/
@chbatey
@chbatey
Production ready applications
• Build: Gradle
• Web framework: Spring boot
• Monitoring: Dropwizard metrics + Graphite
• Testing: Stubbed Cassandra + Cassandra Unit
@chbatey
KillrAuction
• Register users
• Create auctions
• Bid on auctions
• Stream auction bids
https://github.com/chbatey/killrauction
https://github.com/chbatey/cassandra-customer-events
@chbatey
Creating a user
@chbatey
Creating an Auction
@chbatey
Viewing and bidding on an Auction
@chbatey
The tech stack
Storage layer
Front end
Backend end
Interaction
@chbatey
Gradle for building
• Or Maven/SBT
• Download dependencies
• Build + unit test
• Acceptance tests with cucumber
• Load tests with Gatling
@chbatey
Build and deployment
dependencies {

compile("org.springframework.boot:spring-boot-starter-web")

compile("org.springframework.boot:spring-boot-starter-security")

compile("org.springframework.boot:spring-boot-starter-websocket")

compile("org.springframework:spring-websocket:4.2.0.BUILD-SNAPSHOT")

compile("org.springframework:spring-messaging:4.2.0.BUILD-SNAPSHOT")

compile("com.datastax.cassandra:cassandra-driver-core:${cassandraDriverVersion}")

compile("com.datastax.cassandra:cassandra-driver-mapping:${cassandraDriverVersion}")

compile('io.reactivex:rxjava:1.0.8')



// unit + acceptance testing

testCompile("org.springframework.boot:spring-boot-starter-test")

testCompile("info.cukes:cucumber-junit:${cucumberVersion}")

testCompile("info.cukes:cucumber-spring:${cucumberVersion}")

testCompile("org.apache.httpcomponents:httpclient:${apacheHttpVersion}")

testCompile("org.apache.httpcomponents:fluent-hc:${apacheHttpVersion}")



// for performance testing

testCompile "org.scala-lang:scala-library:2.11.5"

testCompile "io.gatling:gatling-app:${gatlingVersion}"ß

}
@chbatey
Languages
• DataStax (open source)
- C#, Java, C++, Python, Node, Ruby
- Very similar programming API
• Other open source
- Go
- Clojure
- Erlang
- Haskell
- Many more Java/Python drivers
- Perl
@chbatey
DataStax Java Driver
• Open source
@chbatey
Dropwizard vs Spring boot
@chbatey
Spring
@chbatey
Configuration
@Component

@ConfigurationProperties(prefix = "cassandra")

public class CassandraConfiguration {



@NotNull

private String[] contactPoints;



@NotNull

private String keyspace;



public String[] getContactPoints() {

return this.contactPoints;

}



public String getKeyspace() {

return keyspace;

}



@Override

public String toString() {

return "CassandraConfiguration{" +

"contactPoints=" + Arrays.toString(contactPoints) +

", keyspace='" + keyspace + ''' +

'}';

}

}

cassandra:

contactPoints:

- localhost

keyspace: killrauction
• SSL?
• Socket options
• Compression
@chbatey
@chbatey
Cluster + Session
@Component

public class CassandraSessionFactoryBean implements FactoryBean<Session> {

@Inject

private CassandraConfiguration cassandraConfiguration;



private Cluster cluster;



@Override

public Session getObject() throws Exception {

cluster = Cluster.builder()

.addContactPoints(cassandraConfiguration.getContactPoints())

.build();

return cluster.connect(cassandraConfiguration.getKeyspace());

}

@Override

public boolean isSingleton() {

return true;

}



@PreDestroy

public void shutdown() {

LOGGER.info("Shutting down cluster");

cluster.close();

}

}
Session per keyspace
Singleton!!
@chbatey
Data modelling time
@chbatey
Cassandra can not join or aggregate
Client
Where do I go for the max?
Modelling in Cassandra
CREATE TABLE customer_events(
customer_id text,
staff_id text,
time timeuuid,
store_type text,
event_type text,
tags map<text, text>,
PRIMARY KEY ((customer_id), time));
Partition Key
Clustering Column(s)
How it is stored on disk
customer
_id
time event_type store_type tags
charles 2014-11-18 16:52:04 basket_add online {'item': 'coffee'}
charles 2014-11-18 16:53:00 basket_add online {'item': ‘wine'}
charles 2014-11-18 16:53:09 logout online {}
chbatey 2014-11-18 16:52:21 login online {}
chbatey 2014-11-18 16:53:21 basket_add online {'item': 'coffee'}
chbatey 2014-11-18 16:54:00 basket_add online {'item': 'cheese'}
charles
event_type
basket_add
staff_id
n/a
store_type
online
tags:item
coffee
event_type
basket_add
staff_id
n/a
store_type
online
tags:item
wine
event_type
logout
staff_id
n/a
store_type
online
chbatey
event_type
login
staff_id
n/a
store_type
online
event_type
basket_add
staff_id
n/a
store_type
online
tags:item
coffee
event_type
basket_add
staff_id
n/a
store_type
online
tags:item
cheese
@chbatey
Requirements
• Store users
• Store auction metadata
• Store auction bids
Lowish throughput
High throughput / reads of many rows
Get top N bids quickly
@chbatey
Designing a schema for users
• Fields
- Username
- First name
- Last name
- Email addresses?
• Unique user names
@chbatey
Users table
CREATE TABLE killrauction.users (
user_name text PRIMARY KEY,
password text,
salt bigint,
first_name text,
last_name text,
emails set<text> ) ;

@chbatey
User DAO
private static final String CREATE_USER_STATEMENT = "INSERT INTO users
(user_name, password, salt, first_name , last_name , emails ) values
(?, ?, ?, ?, ?, ?)";
@PostConstruct

public void prepareStatements() {

createUser = session.prepare(CREATE_USER_STATEMENT);

}

@Inject
private Session session;
@Inject

private Md5PasswordEncoder md5PasswordEncoder;
@Inject

private SecureRandom secureRandom;



private PreparedStatement createUser;



public void createUser(UserCreate userCreate) {

Object salt = secureRandom.nextLong();

String endcodedPassword = md5PasswordEncoder.encodePassword(userCreate.getPassword(), salt);

BoundStatement boundStatement = createUser.bind(userCreate.getUserName(),
endcodedPassword,
salt,
userCreate.getFirstName(), userCreate.getLastName(), userCreate.getEmails());


session.execute(boundStatement);
}
@chbatey
Avoiding overwriting existing users
public boolean createUser(UserCreate userCreate) {

Object salt = secureRandom.nextLong();

String endcodedPassword = md5PasswordEncoder.encodePassword(userCreate.getPassword(), salt);

BoundStatement boundStatement = createUser.bind(userCreate.getUserName(),
endcodedPassword,
salt,
userCreate.getFirstName(), userCreate.getLastName(), userCreate.getEmails());
ResultSet response = session.execute(boundStatement);

boolean applied = response.wasApplied();

return applied;

}
private static final String CREATE_USER_STATEMENT = "INSERT INTO users
(user_name, password, salt, first_name , last_name , emails ) values
(?, ?, ?, ?, ?, ?) IF NOT EXISTS";

@chbatey
AuctionDao
CREATE TABLE IF NOT EXISTS killrauction.auctions (
name text primary key,
owner text,
ends bigint);
@PostConstruct

public void prepareStatements() {

createAuction = session.prepare("insert INTO auctions (name, owner, ends) VALUES (?, ?, ?)");

getAuction = session.prepare("select * from auctions where name = ?");

getAllAuctionSparse = session.prepare("select * from auctions");

}
UUID for auction or use the name? Raise a PR :)
@chbatey
Auction Bids
CREATE TABLE IF NOT EXISTS killrauction.auction_bids (
name TEXT,
bid_time TIMEUUID,
bid_user TEXT,
bid_amount BIGINT,
PRIMARY KEY (name, bid_amount, bid_time ) )
WITH CLUSTERING ORDER BY (bid_amount DESC )
All bids for the same auction on the same node
Bids stored on disk in order of amount
Descending so we can always get the top N bids
@chbatey
Auction Bids
@chbatey
Auction Bids
@PostConstruct

public void prepareStatements() {

createAuction = session.prepare("insert INTO auctions (name, owner, ends) VALUES (?, ?, ?)");

getAuction = session.prepare("select * from auctions where name = ?");

getAllAuctionSparse = session.prepare("select * from auctions”);
getAuctionBids = session.prepare("select * from auction_bids where name = ?");

storeBid = session.prepare("INSERT INTO auction_bids (name, bid_time , bid_amount , bid_user) VALUES ( ?, ?, ?, ?);");

}
public List<Auction> getAllAuctionsSparse() {

BoundStatement bound = getAllAuctionSparse.bind();

return session.execute(bound).all().stream().map(row ->

new Auction(row.getString("name"), row.getString("owner"), Instant.ofEpochMilli(row.getLong("ends"))))

.collect(Collectors.toList());

}
@chbatey
Auction Bids
@PostConstruct

public void prepareStatements() {

createAuction = session.prepare("insert INTO auctions (name, owner, ends) VALUES (?, ?, ?)");

getAuction = session.prepare("select * from auctions where name = ?");

getAllAuctionSparse = session.prepare("select * from auctions”);
getAuctionBids = session.prepare("select * from auction_bids where name = ?");

storeBid = session.prepare("INSERT INTO auction_bids (name, bid_time , bid_amount , bid_user) VALUES ( ?, ?, ?, ?);");

}
public Optional<Auction> getAuction(String auctionName) {

BoundStatement auctionBoundStatement = getAuction.bind(auctionName);

Row auction = session.execute(auctionBoundStatement).one();



BoundStatement bidsBound = getAuctionBids.bind(auctionName);

List<BidVo> bids = session.execute(bidsBound).all().stream().map(row ->

new BidVo(row.getString("bid_user"),

row.getLong("bid_amount"),

UUIDs.unixTimestamp(row.getUUID("bid_time"))))

.collect(Collectors.toList());



return Optional.of(new Auction(auction.getString("name"),

Instant.ofEpochMilli(auction.getLong("ends")),

bids,

auction.getString("owner")));

}
@chbatey
Mapping API
Modelling in Cassandra
CREATE TABLE customer_events(
customer_id text,
staff_id text,
time timeuuid,
store_type text,
event_type text,
tags map<text, text>,
PRIMARY KEY ((customer_id), time));
@chbatey
Mapping API
@Table(keyspace = "customers", name = "customer_events")

public class CustomerEvent {

@PartitionKey

@Column(name = "customer_id")

private String customerId;



@ClusteringColumn

private UUID time;



@Column(name = "staff_id")

private String staffId;



@Column(name = "store_type")

private String storeType;



@Column(name = "event_type")

private String eventType;



private Map<String, String> tags;
// ctr / getters etc
}

@chbatey
Mapping API
@Accessor

public interface CustomerEventDao {

@Query("select * from customers.customer_events where customer_id = :customerId")

Result<CustomerEvent> getCustomerEvents(String customerId);



@Query("select * from customers.customer_events")

Result<CustomerEvent> getAllCustomerEvents();



}


@Bean

public CustomerEventDao customerEventDao() {

MappingManager mappingManager = new MappingManager(session);

return mappingManager.createAccessor(CustomerEventDao.class);

}
@chbatey
Adding some type safety
public enum StoreType {

ONLINE, RETAIL, FRANCHISE, MOBILE

}
@Table(keyspace = "customers", name = "customer_events")

public class CustomerEvent {

@PartitionKey

@Column(name = "customer_id")

private String customerId;



@ClusteringColumn()

private UUID time;



@Column(name = "staff_id")

private String staffId;



@Column(name = "store_type")

@Enumerated(EnumType.STRING) // could be EnumType.ORDINAL

private StoreType storeType;

@chbatey
User defined types
create TYPE store (name text, type text, postcode text) ;





CREATE TABLE customer_events_type(
customer_id text,
staff_id text,
time timeuuid,
store frozen<store>,
event_type text,
tags map<text, text>,
PRIMARY KEY ((customer_id), time));

@chbatey
Mapping user defined types
@UDT(keyspace = "customers", name = "store")

public class Store {

private String name;

private StoreType type;

private String postcode;
// getters etc
}
@Table(keyspace = "customers", name = "customer_events_type")

public class CustomerEventType {

@PartitionKey

@Column(name = "customer_id")

private String customerId;



@ClusteringColumn()

private UUID time;



@Column(name = "staff_id")

private String staffId;



@Frozen

private Store store;



@Column(name = "event_type")

private String eventType;



private Map<String, String> tags;

@chbatey
Mapping user defined types
@UDT(keyspace = "customers", name = "store")

public class Store {

private String name;

private StoreType type;

private String postcode;
// getters etc
}
@Table(keyspace = "customers", name = "customer_events_type")

public class CustomerEventType {

@PartitionKey

@Column(name = "customer_id")

private String customerId;



@ClusteringColumn()

private UUID time;



@Column(name = "staff_id")

private String staffId;



@Frozen

private Store store;



@Column(name = "event_type")

private String eventType;



private Map<String, String> tags;

@chbatey
Dev & Test environments
@chbatey
Development Env + Testing
• Cassandra Unit
- Embeds a single Cassandra node in the same JVM for unit
testing
• https://github.com/jsevellec/cassandra-unit
• Cassandra Cluster Manager
- Spins up small clusters locally
• https://github.com/pcmanus/ccm
• Stubbed Cassandra
- Pretends to be a real Cassandra
@chbatey
Write timeout
Application C
R1
R2
R3
C=QUROUM
Replication factor: 3
timeout
timeout
Write timeout
@chbatey
Unavailable
Application C
R1
R2
R3
C=QUROUM
Replication factor: 3
Unavailable
@chbatey
Example Cassandra Unit with JUnit rule
@Rule

public CassandraUnit cassandraUnitRule = new CassandraUnit(new
ClassPathXmlDataSet("extendedDataSet.xml"));



@Test

public void shouldHaveLoadAnExtendDataSet() throws Exception {

// test dao

}
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId>
<version>2.1.3.1</version>
<scope>test</scope>
</dependency>
@chbatey
Cassandra cluster manager
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns Host ID Rack
UN 127.0.0.1 102.27 KB 256 ? 15ad7694-3e76-4b74-aea0-fa3c0fa59532 rack1
UN 127.0.0.2 102.18 KB 256 ? cca7d0bb-e884-49f9-b098-e38fbe895cbc rack1
UN 127.0.0.3 93.16 KB 256 ? 1f9737d3-c1b8-4df1-be4c-d3b1cced8e30 rack1
UN 127.0.0.4 102.1 KB 256 ? fe27b958-5d3a-4f78-9880-76cb7c9bead1 rack1
UN 127.0.0.5 93.18 KB 256 ? 66eb3f23-8889-44d6-a9e7-ecdd57ed61d0 rack1
UN 127.0.0.6 102.12 KB 256 ? e2e99a7b-c1fb-4f2a-9e4f-7a4666f8245e rack1
ccm create test -v 2.0.5 -n 6 -s
@chbatey
Trace
Execute CQL3 query | 2015-02-02 06:39:58.759000 | 127.0.0.1 | 0
Parsing select * from staff where name in ('chbatey', 'luket', 'jonh'); [SharedPool-Worker-1] | 2015-02-02
06:39:58.766000 | 127.0.0.1 | 7553
Preparing statement [SharedPool-Worker-1] | 2015-02-02 06:39:58.768000 | 127.0.0.1 | 9249
Executing single-partition query on staff [SharedPool-Worker-3] | 2015-02-02 06:39:58.773000 | 127.0.0.1 | 14255
Sending message to /127.0.0.3 [WRITE-/127.0.0.3] | 2015-02-02 06:39:58.773001 | 127.0.0.1 | 14756
Sending message to /127.0.0.5 [WRITE-/127.0.0.5] | 2015-02-02 06:39:58.773001 | 127.0.0.1 | 14928
Sending message to /127.0.0.3 [WRITE-/127.0.0.3] | 2015-02-02 06:39:58.774000 | 127.0.0.1 | 16035
Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.777000 | 127.0.0.5 | 1156
Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.777001 | 127.0.0.5 | 1681
Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.778000 | 127.0.0.5 | 1944
Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.778000 | 127.0.0.3 | 1554
Processing response from /127.0.0.5 [SharedPool-Worker-3] | 2015-02-02 06:39:58.779000 | 127.0.0.1 | 20762
Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.779000 | 127.0.0.3 | 2425
Sending message to /127.0.0.5 [WRITE-/127.0.0.5] | 2015-02-02 06:39:58.779000 | 127.0.0.1 | 21198
Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.779000 | 127.0.0.3 | 2639
Sending message to /127.0.0.6 [WRITE-/127.0.0.6] | 2015-02-02 06:39:58.779000 | 127.0.0.1 | 21208
Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.780000 | 127.0.0.5 | 304
Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.780001 | 127.0.0.5 | 574
Executing single-partition query on staff [SharedPool-Worker-2] | 2015-02-02 06:39:58.781000 | 127.0.0.3 | 4075
Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.781000 | 127.0.0.5 | 708
Enqueuing response to /127.0.0.1 [SharedPool-Worker-2] | 2015-02-02 06:39:58.781001 | 127.0.0.3 | 4348
Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.782000 | 127.0.0.3 | 5371
Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.783000 | 127.0.0.6 | 2463
Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.784000 | 127.0.0.6 | 2905
Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.784001 | 127.0.0.6 | 3160
Processing response from /127.0.0.6 [SharedPool-Worker-2] | 2015-02-02 06:39:58.785000 | 127.0.0.1 | --
Request complete | 2015-02-02 06:39:58.782995 | 127.0.0.1 | 23995
@chbatey
Stubbed Cassandra
Application
Acceptance
test
prime on admin port (REST)
verification on admin port
Admin endpoints
Native protocol
@chbatey
Starting / Stopping
@ClassRule

public static final ScassandraServerRule SCASSANDRA = new
ScassandraServerRule();


Scassandra scassandra = ScassandraFactory.createServer();
scassandra.start();
PrimingClient primingClient = scassandra.primingClient();
ActivityClient activityClient = scassandra.activityClient();
@chbatey
Activity Client
• Query
‣ Query text
‣ Consistency
• PrepreparedStatementExecution
‣ Prepared statement text
‣ Bound variables


public List<Query> retrieveQueries();
public List<PreparedStatementExecution> retrievePreparedStatementExecutions()


public List<Connection> retrieveConnections();
public void clearAllRecordedActivity()


public void clearConnections();

public void clearQueries();

public void clearPreparedStatementExecutions();

@chbatey
Priming Client
• PrimingRequest
‣ Either a Query or PreparedStatement
‣ Query text or QueryPattern (regex)
‣ Consistency (default all)
‣ Result (success, read timeout, unavailable etc)
‣ Rows for successful response
‣ Column types for rows
‣ Variable types for prepared statements
public void prime(PrimingRequest prime)


public List<PrimingRequest> retrievePreparedPrimes()

public List<PrimingRequest> retrieveQueryPrimes()

public void clearAllPrimes()

public void clearQueryPrimes()

public void clearPreparedPrimes()





@chbatey
Testing errors
@Test(expected = UnableToRetrievePeopleException.class)

public void testHandlingOfReadRequestTimeout() throws Exception {

// given

PrimingRequest primeReadRequestTimeout = PrimingRequest.queryBuilder()

.withQuery("select * from person")

.withResult(Result.read_request_timeout)

.build();

primingClient.prime(primeReadRequestTimeout);



//when

underTest.retrievePeople();



//then

} Expecting custom exception
@chbatey
Testing slow connection
@Test(expected = UnableToSavePersonException.class)

public void testThatSlowQueriesTimeout() throws Exception {

// given

PrimingRequest preparedStatementPrime = PrimingRequest.preparedStatementBuilder()

.withQueryPattern("insert into person.*")

.withVariableTypes(VARCHAR, INT, list(TIMESTAMP))

.withFixedDelay(1000)

.build();

primingClient.prime(preparedStatementPrime);

underTest.connect();



underTest.storePerson(new Person("Christopher", 29, Collections.emptyList()));

}
Delays the response by 1000ms
Expect a custom exception
@chbatey
Continuous integration
Unit tests
cassandra unit
stubbed
cassandra
Deploy application to
isolated test
environment with
scaled down
cassandra cluster
Deploy to stage
environment with
same hardware as
production for
capacity testing
Deploy to production
Deploy to stage
integrated
environment +
run E2E tests
@chbatey
Cassandra Stress
@chbatey
Monitoring - Dropwizard metrics
@chbatey
Summary
• Know the eco-system
- Community: Get on IRC / JIRA
- Tools: DataStax drivers, CCM, Cassandra-unit, DevCenter
• Get used to looking at trace
@chbatey
Thanks for listening
• Follow me on twitter @chbatey
• Cassandra + Fault tolerance posts a plenty:
• http://christopher-batey.blogspot.co.uk/
• Cassandra resources: http://planetcassandra.org/

More Related Content

ODP
Android query
PPTX
Azure Table Storage: The Good, the Bad, the Ugly (15 min. lightning talk)
PPTX
Progressive What Apps?
PDF
Kief Morris - Infrastructure is terrible
PDF
Raquel Guimaraes- Third party infrastructure as code
PPTX
SenchaCon 2016: The Once and Future Grid - Nige White
PDF
MongoDB .local Toronto 2019: Using Change Streams to Keep Up with Your Data
PDF
TSAR (TimeSeries AggregatoR) Tech Talk
Android query
Azure Table Storage: The Good, the Bad, the Ugly (15 min. lightning talk)
Progressive What Apps?
Kief Morris - Infrastructure is terrible
Raquel Guimaraes- Third party infrastructure as code
SenchaCon 2016: The Once and Future Grid - Nige White
MongoDB .local Toronto 2019: Using Change Streams to Keep Up with Your Data
TSAR (TimeSeries AggregatoR) Tech Talk

What's hot (19)

PDF
Tsar tech talk
PPTX
Client-side Rendering with AngularJS
PPT
SenchaCon 2016: Add Magic to Your Ext JS Apps with D3 Visualizations - Vitaly...
PPTX
20141001 delapsley-oc-openstack-final
PDF
PDF
Cassandra Day NYC - Cassandra anti patterns
PDF
iOS for ERREST - alternative version
PDF
LA Cassandra Day 2015 - Cassandra for developers
PDF
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
PDF
Persisting Data on SQLite using Room
DOCX
Neolane API Custom SOAP request handler
PPTX
20140821 delapsley-cloudopen-public
PPTX
Hazelcast and MongoDB at Cloud CMS
PDF
Cutting Edge Data Processing with PHP & XQuery
PDF
Micro app-framework
PDF
Elasticsearch und die Java-Welt
PDF
XQuery in the Cloud
PDF
Not your Grandma's XQuery
PDF
MongoDB .local Paris 2020: Adéo @MongoDB : MongoDB Atlas & Leroy Merlin : et ...
Tsar tech talk
Client-side Rendering with AngularJS
SenchaCon 2016: Add Magic to Your Ext JS Apps with D3 Visualizations - Vitaly...
20141001 delapsley-oc-openstack-final
Cassandra Day NYC - Cassandra anti patterns
iOS for ERREST - alternative version
LA Cassandra Day 2015 - Cassandra for developers
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
Persisting Data on SQLite using Room
Neolane API Custom SOAP request handler
20140821 delapsley-cloudopen-public
Hazelcast and MongoDB at Cloud CMS
Cutting Edge Data Processing with PHP & XQuery
Micro app-framework
Elasticsearch und die Java-Welt
XQuery in the Cloud
Not your Grandma's XQuery
MongoDB .local Paris 2020: Adéo @MongoDB : MongoDB Atlas & Leroy Merlin : et ...
Ad

Similar to Cassandra Day London 2015: Getting Started with Apache Cassandra and Java (20)

PDF
Manchester Hadoop Meetup: Spark Cassandra Integration
PDF
Reading Cassandra Meetup Feb 2015: Apache Spark
PDF
3 Dundee-Spark Overview for C* developers
PDF
LJC Conference 2014 Cassandra for Java Developers
PDF
Apache Cassandra and Go
PDF
Munich March 2015 - Cassandra + Spark Overview
PDF
AWS user group Serverless in September - Chris Johnson Bidler "Go Serverless ...
PDF
KSQL - Stream Processing simplified!
PDF
Importing Data into Neo4j quickly and easily - StackOverflow
PDF
Dublin Meetup: Cassandra anti patterns
PDF
Terraform: Cloud Configuration Management (WTC/IPC'16)
PDF
Vienna Feb 2015: Cassandra: How it works and what it's good for!
PDF
A Rusty introduction to Apache Arrow and how it applies to a time series dat...
PDF
Jan 2015 - Cassandra101 Manchester Meetup
PDF
Fears, misconceptions, and accepted anti patterns of a first time cassandra a...
PDF
Paris Day Cassandra: Use case
PDF
DataStax: Making Cassandra Fail (for effective testing)
PDF
Graph Connect: Importing data quickly and easily
PPTX
F&O Summit 2023 Speaker X++ Tips and Tricks.pptx
PDF
GraphConnect Europe 2016 - Importing Data - Mark Needham, Michael Hunger
Manchester Hadoop Meetup: Spark Cassandra Integration
Reading Cassandra Meetup Feb 2015: Apache Spark
3 Dundee-Spark Overview for C* developers
LJC Conference 2014 Cassandra for Java Developers
Apache Cassandra and Go
Munich March 2015 - Cassandra + Spark Overview
AWS user group Serverless in September - Chris Johnson Bidler "Go Serverless ...
KSQL - Stream Processing simplified!
Importing Data into Neo4j quickly and easily - StackOverflow
Dublin Meetup: Cassandra anti patterns
Terraform: Cloud Configuration Management (WTC/IPC'16)
Vienna Feb 2015: Cassandra: How it works and what it's good for!
A Rusty introduction to Apache Arrow and how it applies to a time series dat...
Jan 2015 - Cassandra101 Manchester Meetup
Fears, misconceptions, and accepted anti patterns of a first time cassandra a...
Paris Day Cassandra: Use case
DataStax: Making Cassandra Fail (for effective testing)
Graph Connect: Importing data quickly and easily
F&O Summit 2023 Speaker X++ Tips and Tricks.pptx
GraphConnect Europe 2016 - Importing Data - Mark Needham, Michael Hunger
Ad

More from DataStax Academy (20)

PDF
Forrester CXNYC 2017 - Delivering great real-time cx is a true craft
PPTX
Introduction to DataStax Enterprise Graph Database
PPTX
Introduction to DataStax Enterprise Advanced Replication with Apache Cassandra
PPTX
Cassandra on Docker @ Walmart Labs
PDF
Cassandra 3.0 Data Modeling
PPTX
Cassandra Adoption on Cisco UCS & Open stack
PDF
Data Modeling for Apache Cassandra
PDF
Coursera Cassandra Driver
PDF
Production Ready Cassandra
PDF
Cassandra @ Netflix: Monitoring C* at Scale, Gossip and Tickler & Python
PPTX
Cassandra @ Sony: The good, the bad, and the ugly part 1
PPTX
Cassandra @ Sony: The good, the bad, and the ugly part 2
PDF
Standing Up Your First Cluster
PDF
Real Time Analytics with Dse
PDF
Introduction to Data Modeling with Apache Cassandra
PDF
Cassandra Core Concepts
PPTX
Enabling Search in your Cassandra Application with DataStax Enterprise
PPTX
Bad Habits Die Hard
PDF
Advanced Data Modeling with Apache Cassandra
PDF
Advanced Cassandra
Forrester CXNYC 2017 - Delivering great real-time cx is a true craft
Introduction to DataStax Enterprise Graph Database
Introduction to DataStax Enterprise Advanced Replication with Apache Cassandra
Cassandra on Docker @ Walmart Labs
Cassandra 3.0 Data Modeling
Cassandra Adoption on Cisco UCS & Open stack
Data Modeling for Apache Cassandra
Coursera Cassandra Driver
Production Ready Cassandra
Cassandra @ Netflix: Monitoring C* at Scale, Gossip and Tickler & Python
Cassandra @ Sony: The good, the bad, and the ugly part 1
Cassandra @ Sony: The good, the bad, and the ugly part 2
Standing Up Your First Cluster
Real Time Analytics with Dse
Introduction to Data Modeling with Apache Cassandra
Cassandra Core Concepts
Enabling Search in your Cassandra Application with DataStax Enterprise
Bad Habits Die Hard
Advanced Data Modeling with Apache Cassandra
Advanced Cassandra

Recently uploaded (20)

PDF
From MVP to Full-Scale Product A Startup’s Software Journey.pdf
PPTX
Tartificialntelligence_presentation.pptx
PPTX
Group 1 Presentation -Planning and Decision Making .pptx
PDF
DP Operators-handbook-extract for the Mautical Institute
PDF
project resource management chapter-09.pdf
PPTX
A Presentation on Touch Screen Technology
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Enhancing emotion recognition model for a student engagement use case through...
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PPTX
cloud_computing_Infrastucture_as_cloud_p
PDF
Hindi spoken digit analysis for native and non-native speakers
PDF
Mushroom cultivation and it's methods.pdf
PDF
Transform Your ITIL® 4 & ITSM Strategy with AI in 2025.pdf
PDF
Getting Started with Data Integration: FME Form 101
PDF
A novel scalable deep ensemble learning framework for big data classification...
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Chapter 5: Probability Theory and Statistics
PPTX
A Presentation on Artificial Intelligence
PDF
Assigned Numbers - 2025 - Bluetooth® Document
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
From MVP to Full-Scale Product A Startup’s Software Journey.pdf
Tartificialntelligence_presentation.pptx
Group 1 Presentation -Planning and Decision Making .pptx
DP Operators-handbook-extract for the Mautical Institute
project resource management chapter-09.pdf
A Presentation on Touch Screen Technology
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Enhancing emotion recognition model for a student engagement use case through...
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
cloud_computing_Infrastucture_as_cloud_p
Hindi spoken digit analysis for native and non-native speakers
Mushroom cultivation and it's methods.pdf
Transform Your ITIL® 4 & ITSM Strategy with AI in 2025.pdf
Getting Started with Data Integration: FME Form 101
A novel scalable deep ensemble learning framework for big data classification...
Digital-Transformation-Roadmap-for-Companies.pptx
Chapter 5: Probability Theory and Statistics
A Presentation on Artificial Intelligence
Assigned Numbers - 2025 - Bluetooth® Document
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx

Cassandra Day London 2015: Getting Started with Apache Cassandra and Java

  • 1. Building Java Applications Technical Evangelist for Apache Cassandra @chbatey
  • 2. @chbatey Who am I? • Technical Evangelist for Apache Cassandra •Founder of Stubbed Cassandra •Help out Apache Cassandra users • DataStax •Builds enterprise ready version of Apache Cassandra • Previous: Cassandra backed apps at BSkyB
  • 3. @chbatey Agenda • Everything I wish I knew before I started • Example application: Auction service - Build + Deploy - Tech stack: Spring vs Dropwizard - Configuration - Designing schemas - Example Driver code including LWTs - Continuous Integration + Testing strategies
  • 13. @chbatey Say no to thrift http://exponential.io/blog/2015/01/08/cassandra-terminology/
  • 15. @chbatey Production ready applications • Build: Gradle • Web framework: Spring boot • Monitoring: Dropwizard metrics + Graphite • Testing: Stubbed Cassandra + Cassandra Unit
  • 16. @chbatey KillrAuction • Register users • Create auctions • Bid on auctions • Stream auction bids https://github.com/chbatey/killrauction https://github.com/chbatey/cassandra-customer-events
  • 20. @chbatey The tech stack Storage layer Front end Backend end Interaction
  • 21. @chbatey Gradle for building • Or Maven/SBT • Download dependencies • Build + unit test • Acceptance tests with cucumber • Load tests with Gatling
  • 22. @chbatey Build and deployment dependencies {
 compile("org.springframework.boot:spring-boot-starter-web")
 compile("org.springframework.boot:spring-boot-starter-security")
 compile("org.springframework.boot:spring-boot-starter-websocket")
 compile("org.springframework:spring-websocket:4.2.0.BUILD-SNAPSHOT")
 compile("org.springframework:spring-messaging:4.2.0.BUILD-SNAPSHOT")
 compile("com.datastax.cassandra:cassandra-driver-core:${cassandraDriverVersion}")
 compile("com.datastax.cassandra:cassandra-driver-mapping:${cassandraDriverVersion}")
 compile('io.reactivex:rxjava:1.0.8')
 
 // unit + acceptance testing
 testCompile("org.springframework.boot:spring-boot-starter-test")
 testCompile("info.cukes:cucumber-junit:${cucumberVersion}")
 testCompile("info.cukes:cucumber-spring:${cucumberVersion}")
 testCompile("org.apache.httpcomponents:httpclient:${apacheHttpVersion}")
 testCompile("org.apache.httpcomponents:fluent-hc:${apacheHttpVersion}")
 
 // for performance testing
 testCompile "org.scala-lang:scala-library:2.11.5"
 testCompile "io.gatling:gatling-app:${gatlingVersion}"ß
 }
  • 23. @chbatey Languages • DataStax (open source) - C#, Java, C++, Python, Node, Ruby - Very similar programming API • Other open source - Go - Clojure - Erlang - Haskell - Many more Java/Python drivers - Perl
  • 27. @chbatey Configuration @Component
 @ConfigurationProperties(prefix = "cassandra")
 public class CassandraConfiguration {
 
 @NotNull
 private String[] contactPoints;
 
 @NotNull
 private String keyspace;
 
 public String[] getContactPoints() {
 return this.contactPoints;
 }
 
 public String getKeyspace() {
 return keyspace;
 }
 
 @Override
 public String toString() {
 return "CassandraConfiguration{" +
 "contactPoints=" + Arrays.toString(contactPoints) +
 ", keyspace='" + keyspace + ''' +
 '}';
 }
 }
 cassandra:
 contactPoints:
 - localhost
 keyspace: killrauction • SSL? • Socket options • Compression
  • 29. @chbatey Cluster + Session @Component
 public class CassandraSessionFactoryBean implements FactoryBean<Session> {
 @Inject
 private CassandraConfiguration cassandraConfiguration;
 
 private Cluster cluster;
 
 @Override
 public Session getObject() throws Exception {
 cluster = Cluster.builder()
 .addContactPoints(cassandraConfiguration.getContactPoints())
 .build();
 return cluster.connect(cassandraConfiguration.getKeyspace());
 }
 @Override
 public boolean isSingleton() {
 return true;
 }
 
 @PreDestroy
 public void shutdown() {
 LOGGER.info("Shutting down cluster");
 cluster.close();
 }
 } Session per keyspace Singleton!!
  • 31. @chbatey Cassandra can not join or aggregate Client Where do I go for the max?
  • 32. Modelling in Cassandra CREATE TABLE customer_events( customer_id text, staff_id text, time timeuuid, store_type text, event_type text, tags map<text, text>, PRIMARY KEY ((customer_id), time)); Partition Key Clustering Column(s)
  • 33. How it is stored on disk customer _id time event_type store_type tags charles 2014-11-18 16:52:04 basket_add online {'item': 'coffee'} charles 2014-11-18 16:53:00 basket_add online {'item': ‘wine'} charles 2014-11-18 16:53:09 logout online {} chbatey 2014-11-18 16:52:21 login online {} chbatey 2014-11-18 16:53:21 basket_add online {'item': 'coffee'} chbatey 2014-11-18 16:54:00 basket_add online {'item': 'cheese'} charles event_type basket_add staff_id n/a store_type online tags:item coffee event_type basket_add staff_id n/a store_type online tags:item wine event_type logout staff_id n/a store_type online chbatey event_type login staff_id n/a store_type online event_type basket_add staff_id n/a store_type online tags:item coffee event_type basket_add staff_id n/a store_type online tags:item cheese
  • 34. @chbatey Requirements • Store users • Store auction metadata • Store auction bids Lowish throughput High throughput / reads of many rows Get top N bids quickly
  • 35. @chbatey Designing a schema for users • Fields - Username - First name - Last name - Email addresses? • Unique user names
  • 36. @chbatey Users table CREATE TABLE killrauction.users ( user_name text PRIMARY KEY, password text, salt bigint, first_name text, last_name text, emails set<text> ) ;

  • 37. @chbatey User DAO private static final String CREATE_USER_STATEMENT = "INSERT INTO users (user_name, password, salt, first_name , last_name , emails ) values (?, ?, ?, ?, ?, ?)"; @PostConstruct
 public void prepareStatements() {
 createUser = session.prepare(CREATE_USER_STATEMENT);
 }
 @Inject private Session session; @Inject
 private Md5PasswordEncoder md5PasswordEncoder; @Inject
 private SecureRandom secureRandom;
 
 private PreparedStatement createUser;
 
 public void createUser(UserCreate userCreate) {
 Object salt = secureRandom.nextLong();
 String endcodedPassword = md5PasswordEncoder.encodePassword(userCreate.getPassword(), salt);
 BoundStatement boundStatement = createUser.bind(userCreate.getUserName(), endcodedPassword, salt, userCreate.getFirstName(), userCreate.getLastName(), userCreate.getEmails()); 
 session.execute(boundStatement); }
  • 38. @chbatey Avoiding overwriting existing users public boolean createUser(UserCreate userCreate) {
 Object salt = secureRandom.nextLong();
 String endcodedPassword = md5PasswordEncoder.encodePassword(userCreate.getPassword(), salt);
 BoundStatement boundStatement = createUser.bind(userCreate.getUserName(), endcodedPassword, salt, userCreate.getFirstName(), userCreate.getLastName(), userCreate.getEmails()); ResultSet response = session.execute(boundStatement);
 boolean applied = response.wasApplied();
 return applied;
 } private static final String CREATE_USER_STATEMENT = "INSERT INTO users (user_name, password, salt, first_name , last_name , emails ) values (?, ?, ?, ?, ?, ?) IF NOT EXISTS";

  • 39. @chbatey AuctionDao CREATE TABLE IF NOT EXISTS killrauction.auctions ( name text primary key, owner text, ends bigint); @PostConstruct
 public void prepareStatements() {
 createAuction = session.prepare("insert INTO auctions (name, owner, ends) VALUES (?, ?, ?)");
 getAuction = session.prepare("select * from auctions where name = ?");
 getAllAuctionSparse = session.prepare("select * from auctions");
 } UUID for auction or use the name? Raise a PR :)
  • 40. @chbatey Auction Bids CREATE TABLE IF NOT EXISTS killrauction.auction_bids ( name TEXT, bid_time TIMEUUID, bid_user TEXT, bid_amount BIGINT, PRIMARY KEY (name, bid_amount, bid_time ) ) WITH CLUSTERING ORDER BY (bid_amount DESC ) All bids for the same auction on the same node Bids stored on disk in order of amount Descending so we can always get the top N bids
  • 42. @chbatey Auction Bids @PostConstruct
 public void prepareStatements() {
 createAuction = session.prepare("insert INTO auctions (name, owner, ends) VALUES (?, ?, ?)");
 getAuction = session.prepare("select * from auctions where name = ?");
 getAllAuctionSparse = session.prepare("select * from auctions”); getAuctionBids = session.prepare("select * from auction_bids where name = ?");
 storeBid = session.prepare("INSERT INTO auction_bids (name, bid_time , bid_amount , bid_user) VALUES ( ?, ?, ?, ?);");
 } public List<Auction> getAllAuctionsSparse() {
 BoundStatement bound = getAllAuctionSparse.bind();
 return session.execute(bound).all().stream().map(row ->
 new Auction(row.getString("name"), row.getString("owner"), Instant.ofEpochMilli(row.getLong("ends"))))
 .collect(Collectors.toList());
 }
  • 43. @chbatey Auction Bids @PostConstruct
 public void prepareStatements() {
 createAuction = session.prepare("insert INTO auctions (name, owner, ends) VALUES (?, ?, ?)");
 getAuction = session.prepare("select * from auctions where name = ?");
 getAllAuctionSparse = session.prepare("select * from auctions”); getAuctionBids = session.prepare("select * from auction_bids where name = ?");
 storeBid = session.prepare("INSERT INTO auction_bids (name, bid_time , bid_amount , bid_user) VALUES ( ?, ?, ?, ?);");
 } public Optional<Auction> getAuction(String auctionName) {
 BoundStatement auctionBoundStatement = getAuction.bind(auctionName);
 Row auction = session.execute(auctionBoundStatement).one();
 
 BoundStatement bidsBound = getAuctionBids.bind(auctionName);
 List<BidVo> bids = session.execute(bidsBound).all().stream().map(row ->
 new BidVo(row.getString("bid_user"),
 row.getLong("bid_amount"),
 UUIDs.unixTimestamp(row.getUUID("bid_time"))))
 .collect(Collectors.toList());
 
 return Optional.of(new Auction(auction.getString("name"),
 Instant.ofEpochMilli(auction.getLong("ends")),
 bids,
 auction.getString("owner")));
 }
  • 45. Modelling in Cassandra CREATE TABLE customer_events( customer_id text, staff_id text, time timeuuid, store_type text, event_type text, tags map<text, text>, PRIMARY KEY ((customer_id), time));
  • 46. @chbatey Mapping API @Table(keyspace = "customers", name = "customer_events")
 public class CustomerEvent {
 @PartitionKey
 @Column(name = "customer_id")
 private String customerId;
 
 @ClusteringColumn
 private UUID time;
 
 @Column(name = "staff_id")
 private String staffId;
 
 @Column(name = "store_type")
 private String storeType;
 
 @Column(name = "event_type")
 private String eventType;
 
 private Map<String, String> tags; // ctr / getters etc }

  • 47. @chbatey Mapping API @Accessor
 public interface CustomerEventDao {
 @Query("select * from customers.customer_events where customer_id = :customerId")
 Result<CustomerEvent> getCustomerEvents(String customerId);
 
 @Query("select * from customers.customer_events")
 Result<CustomerEvent> getAllCustomerEvents();
 
 } 
 @Bean
 public CustomerEventDao customerEventDao() {
 MappingManager mappingManager = new MappingManager(session);
 return mappingManager.createAccessor(CustomerEventDao.class);
 }
  • 48. @chbatey Adding some type safety public enum StoreType {
 ONLINE, RETAIL, FRANCHISE, MOBILE
 } @Table(keyspace = "customers", name = "customer_events")
 public class CustomerEvent {
 @PartitionKey
 @Column(name = "customer_id")
 private String customerId;
 
 @ClusteringColumn()
 private UUID time;
 
 @Column(name = "staff_id")
 private String staffId;
 
 @Column(name = "store_type")
 @Enumerated(EnumType.STRING) // could be EnumType.ORDINAL
 private StoreType storeType;

  • 49. @chbatey User defined types create TYPE store (name text, type text, postcode text) ;
 
 
 CREATE TABLE customer_events_type( customer_id text, staff_id text, time timeuuid, store frozen<store>, event_type text, tags map<text, text>, PRIMARY KEY ((customer_id), time));

  • 50. @chbatey Mapping user defined types @UDT(keyspace = "customers", name = "store")
 public class Store {
 private String name;
 private StoreType type;
 private String postcode; // getters etc } @Table(keyspace = "customers", name = "customer_events_type")
 public class CustomerEventType {
 @PartitionKey
 @Column(name = "customer_id")
 private String customerId;
 
 @ClusteringColumn()
 private UUID time;
 
 @Column(name = "staff_id")
 private String staffId;
 
 @Frozen
 private Store store;
 
 @Column(name = "event_type")
 private String eventType;
 
 private Map<String, String> tags;

  • 51. @chbatey Mapping user defined types @UDT(keyspace = "customers", name = "store")
 public class Store {
 private String name;
 private StoreType type;
 private String postcode; // getters etc } @Table(keyspace = "customers", name = "customer_events_type")
 public class CustomerEventType {
 @PartitionKey
 @Column(name = "customer_id")
 private String customerId;
 
 @ClusteringColumn()
 private UUID time;
 
 @Column(name = "staff_id")
 private String staffId;
 
 @Frozen
 private Store store;
 
 @Column(name = "event_type")
 private String eventType;
 
 private Map<String, String> tags;

  • 52. @chbatey Dev & Test environments
  • 53. @chbatey Development Env + Testing • Cassandra Unit - Embeds a single Cassandra node in the same JVM for unit testing • https://github.com/jsevellec/cassandra-unit • Cassandra Cluster Manager - Spins up small clusters locally • https://github.com/pcmanus/ccm • Stubbed Cassandra - Pretends to be a real Cassandra
  • 56. @chbatey Example Cassandra Unit with JUnit rule @Rule
 public CassandraUnit cassandraUnitRule = new CassandraUnit(new ClassPathXmlDataSet("extendedDataSet.xml"));
 
 @Test
 public void shouldHaveLoadAnExtendDataSet() throws Exception {
 // test dao
 } <dependency> <groupId>org.cassandraunit</groupId> <artifactId>cassandra-unit</artifactId> <version>2.1.3.1</version> <scope>test</scope> </dependency>
  • 57. @chbatey Cassandra cluster manager Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns Host ID Rack UN 127.0.0.1 102.27 KB 256 ? 15ad7694-3e76-4b74-aea0-fa3c0fa59532 rack1 UN 127.0.0.2 102.18 KB 256 ? cca7d0bb-e884-49f9-b098-e38fbe895cbc rack1 UN 127.0.0.3 93.16 KB 256 ? 1f9737d3-c1b8-4df1-be4c-d3b1cced8e30 rack1 UN 127.0.0.4 102.1 KB 256 ? fe27b958-5d3a-4f78-9880-76cb7c9bead1 rack1 UN 127.0.0.5 93.18 KB 256 ? 66eb3f23-8889-44d6-a9e7-ecdd57ed61d0 rack1 UN 127.0.0.6 102.12 KB 256 ? e2e99a7b-c1fb-4f2a-9e4f-7a4666f8245e rack1 ccm create test -v 2.0.5 -n 6 -s
  • 58. @chbatey Trace Execute CQL3 query | 2015-02-02 06:39:58.759000 | 127.0.0.1 | 0 Parsing select * from staff where name in ('chbatey', 'luket', 'jonh'); [SharedPool-Worker-1] | 2015-02-02 06:39:58.766000 | 127.0.0.1 | 7553 Preparing statement [SharedPool-Worker-1] | 2015-02-02 06:39:58.768000 | 127.0.0.1 | 9249 Executing single-partition query on staff [SharedPool-Worker-3] | 2015-02-02 06:39:58.773000 | 127.0.0.1 | 14255 Sending message to /127.0.0.3 [WRITE-/127.0.0.3] | 2015-02-02 06:39:58.773001 | 127.0.0.1 | 14756 Sending message to /127.0.0.5 [WRITE-/127.0.0.5] | 2015-02-02 06:39:58.773001 | 127.0.0.1 | 14928 Sending message to /127.0.0.3 [WRITE-/127.0.0.3] | 2015-02-02 06:39:58.774000 | 127.0.0.1 | 16035 Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.777000 | 127.0.0.5 | 1156 Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.777001 | 127.0.0.5 | 1681 Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.778000 | 127.0.0.5 | 1944 Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.778000 | 127.0.0.3 | 1554 Processing response from /127.0.0.5 [SharedPool-Worker-3] | 2015-02-02 06:39:58.779000 | 127.0.0.1 | 20762 Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.779000 | 127.0.0.3 | 2425 Sending message to /127.0.0.5 [WRITE-/127.0.0.5] | 2015-02-02 06:39:58.779000 | 127.0.0.1 | 21198 Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.779000 | 127.0.0.3 | 2639 Sending message to /127.0.0.6 [WRITE-/127.0.0.6] | 2015-02-02 06:39:58.779000 | 127.0.0.1 | 21208 Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.780000 | 127.0.0.5 | 304 Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.780001 | 127.0.0.5 | 574 Executing single-partition query on staff [SharedPool-Worker-2] | 2015-02-02 06:39:58.781000 | 127.0.0.3 | 4075 Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.781000 | 127.0.0.5 | 708 Enqueuing response to /127.0.0.1 [SharedPool-Worker-2] | 2015-02-02 06:39:58.781001 | 127.0.0.3 | 4348 Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.782000 | 127.0.0.3 | 5371 Executing single-partition query on staff [SharedPool-Worker-1] | 2015-02-02 06:39:58.783000 | 127.0.0.6 | 2463 Enqueuing response to /127.0.0.1 [SharedPool-Worker-1] | 2015-02-02 06:39:58.784000 | 127.0.0.6 | 2905 Sending message to /127.0.0.1 [WRITE-/127.0.0.1] | 2015-02-02 06:39:58.784001 | 127.0.0.6 | 3160 Processing response from /127.0.0.6 [SharedPool-Worker-2] | 2015-02-02 06:39:58.785000 | 127.0.0.1 | -- Request complete | 2015-02-02 06:39:58.782995 | 127.0.0.1 | 23995
  • 59. @chbatey Stubbed Cassandra Application Acceptance test prime on admin port (REST) verification on admin port Admin endpoints Native protocol
  • 60. @chbatey Starting / Stopping @ClassRule
 public static final ScassandraServerRule SCASSANDRA = new ScassandraServerRule(); 
 Scassandra scassandra = ScassandraFactory.createServer(); scassandra.start(); PrimingClient primingClient = scassandra.primingClient(); ActivityClient activityClient = scassandra.activityClient();
  • 61. @chbatey Activity Client • Query ‣ Query text ‣ Consistency • PrepreparedStatementExecution ‣ Prepared statement text ‣ Bound variables 
 public List<Query> retrieveQueries(); public List<PreparedStatementExecution> retrievePreparedStatementExecutions() 
 public List<Connection> retrieveConnections(); public void clearAllRecordedActivity() 
 public void clearConnections();
 public void clearQueries();
 public void clearPreparedStatementExecutions();

  • 62. @chbatey Priming Client • PrimingRequest ‣ Either a Query or PreparedStatement ‣ Query text or QueryPattern (regex) ‣ Consistency (default all) ‣ Result (success, read timeout, unavailable etc) ‣ Rows for successful response ‣ Column types for rows ‣ Variable types for prepared statements public void prime(PrimingRequest prime) 
 public List<PrimingRequest> retrievePreparedPrimes()
 public List<PrimingRequest> retrieveQueryPrimes()
 public void clearAllPrimes()
 public void clearQueryPrimes()
 public void clearPreparedPrimes()
 
 

  • 63. @chbatey Testing errors @Test(expected = UnableToRetrievePeopleException.class)
 public void testHandlingOfReadRequestTimeout() throws Exception {
 // given
 PrimingRequest primeReadRequestTimeout = PrimingRequest.queryBuilder()
 .withQuery("select * from person")
 .withResult(Result.read_request_timeout)
 .build();
 primingClient.prime(primeReadRequestTimeout);
 
 //when
 underTest.retrievePeople();
 
 //then
 } Expecting custom exception
  • 64. @chbatey Testing slow connection @Test(expected = UnableToSavePersonException.class)
 public void testThatSlowQueriesTimeout() throws Exception {
 // given
 PrimingRequest preparedStatementPrime = PrimingRequest.preparedStatementBuilder()
 .withQueryPattern("insert into person.*")
 .withVariableTypes(VARCHAR, INT, list(TIMESTAMP))
 .withFixedDelay(1000)
 .build();
 primingClient.prime(preparedStatementPrime);
 underTest.connect();
 
 underTest.storePerson(new Person("Christopher", 29, Collections.emptyList()));
 } Delays the response by 1000ms Expect a custom exception
  • 65. @chbatey Continuous integration Unit tests cassandra unit stubbed cassandra Deploy application to isolated test environment with scaled down cassandra cluster Deploy to stage environment with same hardware as production for capacity testing Deploy to production Deploy to stage integrated environment + run E2E tests
  • 68. @chbatey Summary • Know the eco-system - Community: Get on IRC / JIRA - Tools: DataStax drivers, CCM, Cassandra-unit, DevCenter • Get used to looking at trace
  • 69. @chbatey Thanks for listening • Follow me on twitter @chbatey • Cassandra + Fault tolerance posts a plenty: • http://christopher-batey.blogspot.co.uk/ • Cassandra resources: http://planetcassandra.org/