You are previewing Cassandra: The Definitive Guide.

Cassandra: The Definitive Guide

Cover of Cassandra: The Definitive Guide by Eben Hewitt Published by O'Reilly Media, Inc.
  1. Cassandra: The Definitive Guide
  2. Dedication
  3. SPECIAL OFFER: Upgrade this ebook with O’Reilly
  4. A Note Regarding Supplemental Files
  5. Foreword
  6. Preface
    1. Why Apache Cassandra?
    2. Is This Book for You?
    3. What’s in This Book?
    4. Finding Out More
    5. Conventions Used in This Book
    6. Using Code Examples
    7. Safari® Enabled
    8. How to Contact Us
    9. Acknowledgments
  7. 1. Introducing Cassandra
    1. What’s Wrong with Relational Databases?
    2. A Quick Review of Relational Databases
      1. RDBMS: The Awesome and the Not-So-Much
      2. Web Scale
    3. The Cassandra Elevator Pitch
      1. Cassandra in 50 Words or Less
      2. Distributed and Decentralized
      3. Elastic Scalability
      4. High Availability and Fault Tolerance
      5. Tuneable Consistency
      6. Brewer’s CAP Theorem
      7. Row-Oriented
      8. Schema-Free
      9. High Performance
    4. Where Did Cassandra Come From?
    5. Use Cases for Cassandra
      1. Large Deployments
      2. Lots of Writes, Statistics, and Analysis
      3. Geographical Distribution
      4. Evolving Applications
    6. Who Is Using Cassandra?
    7. Summary
  8. 2. Installing Cassandra
    1. Installing the Binary
      1. Extracting the Download
      2. What’s In There?
    2. Building from Source
      1. Additional Build Targets
      2. Building with Maven
    3. Running Cassandra
      1. On Windows
      2. On Linux
      3. Starting the Server
    4. Running the Command-Line Client Interface
    5. Basic CLI Commands
      1. Help
      2. Connecting to a Server
      3. Describing the Environment
      4. Creating a Keyspace and Column Family
      5. Writing and Reading Data
    6. Summary
  9. 3. The Cassandra Data Model
    1. The Relational Data Model
    2. A Simple Introduction
    3. Clusters
    4. Keyspaces
    5. Column Families
      1. Column Family Options
    6. Columns
      1. Wide Rows, Skinny Rows
      2. Column Sorting
    7. Super Columns
      1. Composite Keys
    8. Design Differences Between RDBMS and Cassandra
      1. No Query Language
      2. No Referential Integrity
      3. Secondary Indexes
      4. Sorting Is a Design Decision
      5. Denormalization
    9. Design Patterns
      1. Materialized View
      2. Valueless Column
      3. Aggregate Key
    10. Some Things to Keep in Mind
    11. Summary
  10. 4. Sample Application
    1. Data Design
    2. Hotel App RDBMS Design
    3. Hotel App Cassandra Design
    4. Hotel Application Code
      1. Creating the Database
      2. Data Structures
      3. Getting a Connection
      4. Prepopulating the Database
      5. The Search Application
    5. Twissandra
    6. Summary
  11. 5. The Cassandra Architecture
    1. System Keyspace
    2. Peer-to-Peer
    3. Gossip and Failure Detection
    4. Anti-Entropy and Read Repair
    5. Memtables, SSTables, and Commit Logs
    6. Hinted Handoff
    7. Compaction
    8. Bloom Filters
    9. Tombstones
    10. Staged Event-Driven Architecture (SEDA)
    11. Managers and Services
      1. Cassandra Daemon
      2. Storage Service
      3. Messaging Service
      4. Hinted Handoff Manager
    12. Summary
  12. 6. Configuring Cassandra
    1. Keyspaces
      1. Creating a Column Family
      2. Transitioning from 0.6 to 0.7
    2. Replicas
    3. Replica Placement Strategies
      1. Simple Strategy
      2. Old Network Topology Strategy
      3. Network Topology Strategy
    4. Replication Factor
      1. Increasing the Replication Factor
    5. Partitioners
      1. Random Partitioner
      2. Order-Preserving Partitioner
      3. Collating Order-Preserving Partitioner
      4. Byte-Ordered Partitioner
    6. Snitches
      1. Simple Snitch
      2. PropertyFileSnitch
    7. Creating a Cluster
      1. Changing the Cluster Name
      2. Adding Nodes to a Cluster
      3. Multiple Seed Nodes
    8. Dynamic Ring Participation
    9. Security
      1. Using SimpleAuthenticator
      2. Programmatic Authentication
      3. Using MD5 Encryption
      4. Providing Your Own Authentication
    10. Miscellaneous Settings
    11. Additional Tools
      1. Viewing Keys
      2. Importing Previous Configurations
    12. Summary
  13. 7. Reading and Writing Data
    1. Query Differences Between RDBMS and Cassandra
      1. No Update Query
      2. Record-Level Atomicity on Writes
      3. No Server-Side Transaction Support
      4. No Duplicate Keys
    2. Basic Write Properties
    3. Consistency Levels
    4. Basic Read Properties
    5. The API
      1. Ranges and Slices
    6. Setup and Inserting Data
    7. Using a Simple Get
    8. Seeding Some Values
    9. Slice Predicate
      1. Getting Particular Column Names with Get Slice
      2. Getting a Set of Columns with Slice Range
      3. Getting All Columns in a Row
    10. Get Range Slices
    11. Multiget Slice
    12. Deleting
    13. Batch Mutates
      1. Batch Deletes
      2. Range Ghosts
    14. Programmatically Defining Keyspaces and Column Families
    15. Summary
  14. 8. Clients
    1. Basic Client API
    2. Thrift
      1. Thrift Support for Java
      2. Exceptions
      3. Thrift Summary
    3. Avro
      1. Avro Ant Targets
      2. Avro Specification
      3. Avro Summary
    4. A Bit of Git
    5. Connecting Client Nodes
      1. Client List
      2. Round-Robin DNS
      3. Load Balancer
    6. Cassandra Web Console
    7. Hector (Java)
      1. Features
      2. The Hector API
    8. HectorSharp (C#)
    9. Chirper
    10. Chiton (Python)
    11. Pelops (Java)
    12. Kundera (Java ORM)
    13. Fauna (Ruby)
    14. Summary
  15. 9. Monitoring
    1. Logging
      1. Tailing
      2. General Tips
    2. Overview of JMX and MBeans
      1. MBeans
      2. Integrating JMX
    3. Interacting with Cassandra via JMX
    4. Cassandra’s MBeans
      1. org.apache.cassandra.concurrent
      2. org.apache.cassandra.db
      3. org.apache.cassandra.gms
      4. org.apache.cassandra.service
    5. Custom Cassandra MBeans
    6. Runtime Analysis Tools
      1. Heap Analysis with JMX and JHAT
      2. Detecting Thread Problems
    7. Health Check
    8. Summary
  16. 10. Maintenance
    1. Getting Ring Information
      1. Info
      2. Ring
    2. Getting Statistics
      1. Using cfstats
      2. Using tpstats
    3. Basic Maintenance
      1. Repair
      2. Flush
      3. Cleanup
    4. Snapshots
      1. Taking a Snapshot
      2. Clearing a Snapshot
    5. Load-Balancing the Cluster
      1. loadbalance and streams
    6. Decommissioning a Node
    7. Updating Nodes
      1. Removing Tokens
      2. Compaction Threshold
      3. Changing Column Families in a Working Cluster
    8. Summary
  17. 11. Performance Tuning
    1. Data Storage
    2. Reply Timeout
    3. Commit Logs
    4. Memtables
    5. Concurrency
    6. Caching
    7. Buffer Sizes
    8. Using the Python Stress Test
      1. Generating the Python Thrift Interfaces
      2. Running the Python Stress Test
    9. Startup and JVM Settings
      1. Tuning the JVM
    10. Summary
  18. 12. Integrating Hadoop
    1. What Is Hadoop?
    2. Working with MapReduce
      1. Cassandra Hadoop Source Package
    3. Running the Word Count Example
      1. Outputting Data to Cassandra
      2. Hadoop Streaming
    4. Tools Above MapReduce
      1. Pig
      2. Hive
    5. Cluster Configuration
    6. Use Cases
      1. Raptr.com: Keith Thornhill
      2. Imagini: Dave Gardner
    7. Summary
  19. A. The Nonrelational Landscape
    1. Nonrelational Databases
    2. Object Databases
    3. XML Databases
      1. SoftwareAG Tamino
      2. eXist
      3. Oracle Berkeley XML DB
      4. MarkLogic Server
      5. Apache Xindice
      6. Summary
    4. Document-Oriented Databases
      1. IBM Lotus
      2. Apache CouchDB
      3. MongoDB
      4. Riak
    5. Graph Databases
      1. FlockDB
      2. Neo4J
    6. Key-Value Stores and Distributed Hashtables
      1. Amazon Dynamo
      2. Project Voldemort
      3. Redis
    7. Columnar Databases
      1. Google Bigtable
      2. HBase
      3. Hypertable
      4. Polyglot Persistence
    8. Summary
  20. Glossary
  21. Index
  22. About the Author
  23. Colophon
  24. SPECIAL OFFER: Upgrade this ebook with O’Reilly
  25. Copyright
O'Reilly logo

Chapter 4. Sample Application

In this chapter, we create a complete sample application so we can see how all the parts fit together. We will use various parts of the API to see how to insert data, perform batch updates, and search column families and super column families.

To create the example, we want to use something that is complex enough to show the various data structures and basic API operations, but not something that will bog you down with details. In order to get enough data in the database to make our searches work right (by finding stuff we’re looking for and leaving out stuff we’re not looking for), there’s a little redundancy in the prepopulation of the database. Also, I wanted to use a domain that’s familiar to everyone so we can concentrate on how to work with Cassandra, not on what the application domain is all about.

Note

The code in this chapter has been tested against the 0.7 beta 1 release, and works as shown. It is possible that API changes may necessitate minor tuning on your part, depending on your version.

Data Design

When you set out to build a new data-driven application that will use a relational database, you might start by modeling the domain as a set of properly normalized tables and use foreign keys to reference related data in other tables. Now that we have an understanding of how Cassandra stores data, let’s create a little domain model that is easy to understand in the relational world, and then see how we might map it from a relational to a distributed hashtable model in Cassandra.

Relational modeling, in simple terms, means that you start from the conceptual domain and then represent the nouns in the domain in tables. You then assign primary keys and foreign keys to model relationships. When you have a many-to-many relationship, you create the join tables that represent just those keys. The join tables don’t exist in the real world, and are a necessary side effect of the way relational models work. After you have all your tables laid out, you can start writing queries that pull together disparate data using the relationships defined by the keys. The queries in the relational world are very much secondary. It is assumed that you can always get the data you want as long as you have your tables modeled properly. Even if you have to use several complex subqueries or join statements, this is usually true.

By contrast, in Cassandra you don’t start with the data model; you start with the query model.

For this example, let’s use a domain that is easily understood and that everyone can relate to: a hotel that wants to allow guests to book a reservation.

Our conceptual domain includes hotels, guests that stay in the hotels, a collection of rooms for each hotel, and a record of the reservation, which is a certain guest in a certain room for a certain period of time (called the “stay”). Hotels typically also maintain a collection of “points of interest,” which are parks, museums, shopping galleries, monuments, or other places near the hotel that guests might want to visit during their stay. Both hotels and points of interest need to maintain geolocation data so that they can be found on maps for mashups, and to calculate distances.

Note

Obviously, in the real world there would be many more considerations and much more complexity. For example, hotel rates are notoriously dynamic, and calculating them involves a wide array of factors. Here we’re defining something complex enough to be interesting and touch on the important points, but simple enough to maintain the focus on learning Cassandra.

Here’s how we would start this application design with Cassandra. First, determine your queries. We’ll likely have something like the following:

  • Find hotels in a given area.

  • Find information about a given hotel, such as its name and location.

  • Find points of interest near a given hotel.

  • Find an available room in a given date range.

  • Find the rate and amenities for a room.

  • Book the selected room by entering guest information.

Hotel App RDBMS Design

Figure 4-1 shows how we might represent this simple hotel reservation system using a relational database model. The relational model includes a couple of “join” tables in order to resolve the many-to-many relationships of hotels-to-points of interest, and for rooms-to-amenities.

A simple hotel search system using RDBMS

Figure 4-1. A simple hotel search system using RDBMS

Hotel App Cassandra Design

Although there are many possible ways to do it, we could represent the same logical data model using a Cassandra physical model such as that shown in Figure 4-2.

The hotel search represented with Cassandra’s model

Figure 4-2. The hotel search represented with Cassandra’s model

In this design, we’re doing all the same things as in the relational design. We have transferred some of the tables, such as Hotel and Guest, to column families. Other tables, such as PointOfInterest, have been denormalized into a super column family. In the relational model, you can look up hotels by the city they’re in using a SQL statement. But because we don’t have SQL in Cassandra, we’ve created an index in the form of the HotelByCity column family.

Note

I’m using a stereotype notation here, so <<CF>> refers to a column family, <<SCF>> refers to a super column family, and so on.

We have combined room and amenities into a single column family, Room. The columns such as type and rate will have corresponding values; other columns, such as hot tub, will just use the presence of the column name itself as the value, and be otherwise empty.

Hotel Application Code

In this section we walk through the code and show how to implement the given design. This is useful because it illustrates several different API functions in action.

Warning

The purpose of this sample application is to show how different ideas in Cassandra can be combined. It is by no means the only way to transfer the relational design into this model. There is a lot of low-level plumbing here that uses the Thrift API. Thrift is (probably) changing to Avro, so although the basic ideas here work, you don’t want to follow this example in a real application. Instead, check out Chapter 8 and use one of the many available third-party clients for Cassandra, depending on the language that your application uses and other needs that you have.

The application we’re building will do the following things:

  1. Create the database structure.

  2. Prepopulate the database with hotel and point of interest data. The hotels are stored in standard column families, and the points of interest are in super column families.

  3. Search for a list of hotels in a given city. This uses a secondary index.

  4. Select one of the hotels returned in the search, and then search for a list of points of interest near the chosen hotel.

  5. Booking the hotel by doing an insert into the Reservation column family should be straightforward at this point, and is left to the reader.

Space doesn’t permit implementing the entire application. But we’ll walk through the major parts, and finishing the implementation is just a matter of creating a variation of what is shown.

Creating the Database

The first step is creating the schema definition. For this example, we’ll define the schema in YAML and then load it, although you could also use client code to define it.

The YAML file shown in Example 4-1 defines the necessary keyspace and column families.

Example 4-1. Schema definition in cassandra.yaml

keyspaces:

    - name: Hotelier
      replica_placement_strategy: org.apache.cassandra.locator.RackUnawareStrategy
      replication_factor: 1
      column_families:
        - name: Hotel
          compare_with: UTF8Type

        - name: HotelByCity
          compare_with: UTF8Type

        - name: Guest
          compare_with: BytesType

        - name: Reservation
          compare_with: TimeUUIDType

        - name: PointOfInterest
          column_type: Super
          compare_with: UTF8Type
          compare_subcolumns_with: UTF8Type

        - name: Room
          column_type: Super
          compare_with: BytesType
          compare_subcolumns_with: BytesType

        - name: RoomAvailability
          column_type: Super
          compare_with: BytesType
          compare_subcolumns_with: BytesType

This definition provides all of the column families to run the example, and a couple more that we don’t directly reference in the application code, because it rounds out the design as transferred from RDBMS.

Loading the schema

Once you have the schema defined in YAML, you need to load it. To do this, open a console, start the jconsole application, and connect to Cassandra via JMX. Then, execute the operation loadSchemaFromYAML, which is part of the org.apache.cassandra.service.StorageService MBean. Now Cassandra knows about your schema and you can start using it. You can also use the API itself to create keyspaces and column families.

Data Structures

The application requires some standard data structures that will just act as transfer objects for us. These aren’t particularly interesting, but are required to keep things organized. We’ll use a Hotel data structure to hold all of the information about a hotel, shown in Example 4-2.

Example 4-2. Hotel.java

package com.cassandraguide.hotel;

//data transfer object
public class Hotel {
	public String id;
	public String name;
	public String phone;
	public String address;
	public String city;
	public String state;
	public String zip;
}

This structure just holds the column information for convenience in the application.

We also have a POI data structure to hold information about points of interest. This is shown in Example 4-3.

Example 4-3. POI.java

package com.cassandraguide.hotel;

//data transfer object for a Point of Interest
public class POI {
	public String name; 
	public String desc; 
	public String phone;
}

We also have a Constants class, which keeps commonly used strings in one easy-to-change place, shown in Example 4-4.

Example 4-4. Constants.java

package com.cassandraguide.hotel;

import org.apache.cassandra.thrift.ConsistencyLevel;

public class Constants {
	
	public static final String CAMBRIA_NAME = "Cambria Suites Hayden";
	public static final String CLARION_NAME= "Clarion Scottsdale Peak";
	public static final String W_NAME = "The W SF";
	public static final String WALDORF_NAME = "The Waldorf=Astoria";

	public static final String UTF8 = "UTF8";
	public static final String KEYSPACE = "Hotelier";
	public static final ConsistencyLevel CL = ConsistencyLevel.ONE;
	public static final String HOST = "localhost";
	public static final int PORT = 9160;
}

Holding these commonly used strings make the code clearer and more concise, and you can easily change these values to reflect what makes sense in your environment.

Getting a Connection

For convenience, and to save repeating a bunch of boilerplate code, let’s put the connection code into one class, called Connector, shown in Example 4-5.

Example 4-5. A connection client convenience class, Connector.java

package com.cassandraguide.hotel;

import static com.cassandraguide.hotel.Constants.KEYSPACE;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

//simple convenience class to wrap connections, just to reduce repeat code
public class Connector {
	
	TTransport tr = new TSocket("localhost", 9160);
	
	// returns a new connection to our keyspace
	public Cassandra.Client connect() throws TTransportException,
			TException, InvalidRequestException {

		TFramedTransport tf = new TFramedTransport(tr);
		TProtocol proto = new TBinaryProtocol(tf);
		Cassandra.Client client = new Cassandra.Client(proto);
		tr.open();
		client.set_keyspace(KEYSPACE);
		return client;
	}
	
	public void close() {
		tr.close();
	}
}

When we need to execute a database operation, we can use this class to open a connection and then close the connection once we’re done.

Prepopulating the Database

The Prepopulate class, shown in Example 4-6, does a bunch of inserts and batch_mutates in order to prepopulate the database with the hotel information and points of interest information that users will search for.

Example 4-6. Prepopulate.java

package com.cassandraguide.hotel;

import static com.cassandraguide.hotel.Constants.CAMBRIA_NAME;
import static com.cassandraguide.hotel.Constants.CL;
import static com.cassandraguide.hotel.Constants.CLARION_NAME;
import static com.cassandraguide.hotel.Constants.UTF8;
import static com.cassandraguide.hotel.Constants.WALDORF_NAME;
import static com.cassandraguide.hotel.Constants.W_NAME;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Clock;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.Mutation;
import org.apache.cassandra.thrift.SuperColumn;
import org.apache.log4j.Logger;

/**
 * Performs the initial population of the database. 
 * Fills the CFs and SCFs with Hotel, Point of Interest, and index data.
 * Shows batch_mutate and insert for Column Families and Super Column Families.
 * 
 * I am totally ignoring exceptions to save space.
 */
public class Prepopulate {
	private static final Logger LOG = Logger.getLogger(Prepopulate.class);
	
	
	private Cassandra.Client client;
	private Connector connector;
	
	//constructor opens a connection so we don't have to 
	//constantly recreate it
	public Prepopulate() throws Exception {
		connector = new Connector();
		client = connector.connect();
	}

	void prepopulate() throws Exception {
		//pre-populate the DB with Hotels
		insertAllHotels();
		
		//also add all hotels to index to help searches
		insertByCityIndexes();
		
		//pre-populate the DB with POIs
		insertAllPointsOfInterest();
		
		connector.close();
	}
	
	//also add hotels to lookup by city index
	public void insertByCityIndexes() throws Exception  {
	
		String scottsdaleKey = "Scottsdale:AZ"; 
		String sfKey = "San Francisco:CA"; 
		String newYorkKey = "New York:NY"; 
					
		insertByCityIndex(scottsdaleKey, CAMBRIA_NAME);
		insertByCityIndex(scottsdaleKey, CLARION_NAME);
		insertByCityIndex(sfKey, W_NAME);
		insertByCityIndex(newYorkKey, WALDORF_NAME);
}
	//use Valueless Column pattern
	private void insertByCityIndex(String rowKey, String hotelName)
		throws Exception {
				
		Clock clock = new Clock(System.nanoTime());
		
		Column nameCol = new Column(hotelName.getBytes(UTF8), 
				new byte[0], clock);
		
		ColumnOrSuperColumn nameCosc = new ColumnOrSuperColumn();
		nameCosc.column = nameCol;
		
		Mutation nameMut = new Mutation();
		nameMut.column_or_supercolumn = nameCosc;

		//set up the batch
		Map<String, Map<String, List<Mutation>>> mutationMap = 
			new HashMap<String, Map<String, List<Mutation>>>();
		
		Map<String, List<Mutation>> muts = 
			new HashMap<String, List<Mutation>>();
		List<Mutation> cols = new ArrayList<Mutation>();
		cols.add(nameMut);

		String columnFamily = "HotelByCity";
		muts.put(columnFamily, cols);
		
		//outer map key is a row key
		//inner map key is the column family name
		mutationMap.put(rowKey, muts);
		
		
		//create representation of the column
		ColumnPath cp = new ColumnPath(columnFamily);
		cp.setColumn(hotelName.getBytes(UTF8));
		
		ColumnParent parent = new ColumnParent(columnFamily);
		//here, the column name IS the value (there's no value)
		Column col = new Column(hotelName.getBytes(UTF8), new byte[0], clock);
		
		client.insert(rowKey.getBytes(), parent, col, CL);
				
		LOG.debug("Inserted HotelByCity index for " + hotelName);
		
	} //end inserting ByCity index

	//POI
	public void insertAllPointsOfInterest() throws Exception {

		LOG.debug("Inserting POIs.");
					
		insertPOIEmpireState();
		insertPOICentralPark();
		insertPOIPhoenixZoo();
		insertPOISpringTraining();
		
		LOG.debug("Done inserting POIs.");
	}

	private void insertPOISpringTraining() throws Exception {
		//Map<byte[],Map<String,List<Mutation>>>
		Map<byte[], Map<String, List<Mutation>>> outerMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		List<Mutation> columnsToAdd = new ArrayList<Mutation>();
		
		Clock clock = new Clock(System.nanoTime());
		String keyName = "Spring Training";
		Column descCol = new Column("desc".getBytes(UTF8), 
			"Fun for baseball fans.".getBytes("UTF-8"), clock);
		Column phoneCol = new Column("phone".getBytes(UTF8),
				"623-333-3333".getBytes(UTF8), clock);
		
		List<Column> cols = new ArrayList<Column>();
		cols.add(descCol);
		cols.add(phoneCol);
		
		Map<String, List<Mutation>> innerMap = 
			new HashMap<String, List<Mutation>>();
		
		Mutation columns = new Mutation();
		ColumnOrSuperColumn descCosc = new ColumnOrSuperColumn();
		SuperColumn sc = new SuperColumn();
		sc.name = CAMBRIA_NAME.getBytes();
		sc.columns = cols;
		
		descCosc.super_column = sc;
		columns.setColumn_or_supercolumn(descCosc);
		
		columnsToAdd.add(columns);
		
		String superCFName = "PointOfInterest";
		ColumnPath cp = new ColumnPath();
		cp.column_family = superCFName;
		cp.setSuper_column(CAMBRIA_NAME.getBytes());
		cp.setSuper_columnIsSet(true);
		
		innerMap.put(superCFName, columnsToAdd);
		outerMap.put(keyName.getBytes(), innerMap);
		
		client.batch_mutate(outerMap, CL);
				
		LOG.debug("Done inserting Spring Training.");
	}


	private void insertPOIPhoenixZoo() throws Exception {
		
		Map<byte[], Map<String, List<Mutation>>> outerMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		List<Mutation> columnsToAdd = new ArrayList<Mutation>();
		
		long ts = System.currentTimeMillis();
		String keyName = "Phoenix Zoo";
		Column descCol = new Column("desc".getBytes(UTF8), 
			"They have animals here.".getBytes("UTF-8"), new Clock(ts));

		Column phoneCol = new Column("phone".getBytes(UTF8),
				"480-555-9999".getBytes(UTF8), new Clock(ts));
		
		List<Column> cols = new ArrayList<Column>();
		cols.add(descCol);
		cols.add(phoneCol);
		
		Map<String, List<Mutation>> innerMap = 
			new HashMap<String, List<Mutation>>();
		
		String cambriaName = "Cambria Suites Hayden";
		
		Mutation columns = new Mutation();
		ColumnOrSuperColumn descCosc = new ColumnOrSuperColumn();
		SuperColumn sc = new SuperColumn();
		sc.name = cambriaName.getBytes();
		sc.columns = cols;
		
		descCosc.super_column = sc;
		columns.setColumn_or_supercolumn(descCosc);
		
		columnsToAdd.add(columns);
		
		String superCFName = "PointOfInterest";
		ColumnPath cp = new ColumnPath();
		cp.column_family = superCFName;
		cp.setSuper_column(cambriaName.getBytes());
		cp.setSuper_columnIsSet(true);
		
		innerMap.put(superCFName, columnsToAdd);
		outerMap.put(keyName.getBytes(), innerMap);
		
		client.batch_mutate(outerMap, CL);
				
		LOG.debug("Done inserting Phoenix Zoo.");
	}


	private void insertPOICentralPark() throws Exception {
					
		Map<byte[], Map<String, List<Mutation>>> outerMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		List<Mutation> columnsToAdd = new ArrayList<Mutation>();
		
		Clock clock = new Clock(System.nanoTime());
		String keyName = "Central Park";
		Column descCol = new Column("desc".getBytes(UTF8), 
			"Walk around in the park. It's pretty.".getBytes("UTF-8"), clock);

		//no phone column for park
		
		List<Column> cols = new ArrayList<Column>();
		cols.add(descCol);
		
		Map<String, List<Mutation>> innerMap = 
			new HashMap<String, List<Mutation>>();
		
		Mutation columns = new Mutation();
		ColumnOrSuperColumn descCosc = new ColumnOrSuperColumn();
		SuperColumn waldorfSC = new SuperColumn();
		waldorfSC.name = WALDORF_NAME.getBytes();
		waldorfSC.columns = cols;
		
		descCosc.super_column = waldorfSC;
		columns.setColumn_or_supercolumn(descCosc);
		
		columnsToAdd.add(columns);
		
		String superCFName = "PointOfInterest";
		ColumnPath cp = new ColumnPath();
		cp.column_family = superCFName;
		cp.setSuper_column(WALDORF_NAME.getBytes());
		cp.setSuper_columnIsSet(true);
		
		innerMap.put(superCFName, columnsToAdd);
		outerMap.put(keyName.getBytes(), innerMap);

		client.batch_mutate(outerMap, CL);
				
		LOG.debug("Done inserting Central Park.");
	}

	private void insertPOIEmpireState()  throws Exception {
		
		Map<byte[], Map<String, List<Mutation>>> outerMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		
		List<Mutation> columnsToAdd = new ArrayList<Mutation>();
		
		Clock clock = new Clock(System.nanoTime());
		String esbName = "Empire State Building";
		Column descCol = new Column("desc".getBytes(UTF8), 
					"Great view from 102nd floor.".getBytes("UTF-8"), clock);
		Column phoneCol = new Column("phone".getBytes(UTF8),
					"212-777-7777".getBytes(UTF8), clock);
		
		List<Column> esbCols = new ArrayList<Column>();
		esbCols.add(descCol);
		esbCols.add(phoneCol);
		
		Map<String, List<Mutation>> innerMap = new HashMap<String, List<Mutation>>();
					
		Mutation columns = new Mutation();
		ColumnOrSuperColumn descCosc = new ColumnOrSuperColumn();
		SuperColumn waldorfSC = new SuperColumn();
		waldorfSC.name = WALDORF_NAME.getBytes();
		waldorfSC.columns = esbCols;
		
		descCosc.super_column = waldorfSC;
		columns.setColumn_or_supercolumn(descCosc);
		
		columnsToAdd.add(columns);
		
		String superCFName = "PointOfInterest";
		ColumnPath cp = new ColumnPath();
		cp.column_family = superCFName;
		cp.setSuper_column(WALDORF_NAME.getBytes());
		cp.setSuper_columnIsSet(true);
		
		innerMap.put(superCFName, columnsToAdd);
		outerMap.put(esbName.getBytes(), innerMap);
		
		client.batch_mutate(outerMap, CL);
				
		LOG.debug("Done inserting Empire State.");
	}
	
	//convenience method runs all of the individual inserts
	public void insertAllHotels() throws Exception {
		
		String columnFamily = "Hotel";	
		
		//row keys
		String cambriaKey = "AZC_043"; 
		String clarionKey = "AZS_011"; 
		String wKey = "CAS_021"; 
		String waldorfKey = "NYN_042"; 
		
		//conveniences
		Map<byte[], Map<String, List<Mutation>>> cambriaMutationMap = 
			createCambriaMutation(columnFamily, cambriaKey);
		
		Map<byte[], Map<String, List<Mutation>>> clarionMutationMap = 
			createClarionMutation(columnFamily, clarionKey);
		
		Map<byte[], Map<String, List<Mutation>>> waldorfMutationMap = 
			createWaldorfMutation(columnFamily, waldorfKey);

		Map<byte[], Map<String, List<Mutation>>> wMutationMap = 
			createWMutation(columnFamily, wKey);
			
		client.batch_mutate(cambriaMutationMap, CL);
		LOG.debug("Inserted " + cambriaKey);
		client.batch_mutate(clarionMutationMap, CL);
		LOG.debug("Inserted " + clarionKey);
		client.batch_mutate(wMutationMap, CL);
		LOG.debug("Inserted " + wKey);
		client.batch_mutate(waldorfMutationMap, CL);
		LOG.debug("Inserted " + waldorfKey);
				
		LOG.debug("Done inserting at " + System.nanoTime());
	}

	//set up columns to insert for W
	private Map<byte[], Map<String, List<Mutation>>> createWMutation(
			String columnFamily, String rowKey)
			throws UnsupportedEncodingException {
		
		Clock clock = new Clock(System.nanoTime());
		
		Column nameCol = new Column("name".getBytes(UTF8), 
				W_NAME.getBytes("UTF-8"), clock);
		Column phoneCol = new Column("phone".getBytes(UTF8),
				"415-222-2222".getBytes(UTF8), clock);
		Column addressCol = new Column("address".getBytes(UTF8), 
				"181 3rd Street".getBytes(UTF8), clock);
		Column cityCol = new Column("city".getBytes(UTF8),
				"San Francisco".getBytes(UTF8), clock);
		Column stateCol = new Column("state".getBytes(UTF8), 
				"CA".getBytes("UTF-8"), clock);
		Column zipCol = new Column("zip".getBytes(UTF8), 
				"94103".getBytes(UTF8), clock);
		
		ColumnOrSuperColumn nameCosc = new ColumnOrSuperColumn();
		nameCosc.column = nameCol;
		
		ColumnOrSuperColumn phoneCosc = new ColumnOrSuperColumn();
		phoneCosc.column = phoneCol;
		
		ColumnOrSuperColumn addressCosc = new ColumnOrSuperColumn();
		addressCosc.column = addressCol;

		ColumnOrSuperColumn cityCosc = new ColumnOrSuperColumn();
		cityCosc.column = cityCol;

		ColumnOrSuperColumn stateCosc = new ColumnOrSuperColumn();
		stateCosc.column = stateCol;
		
		ColumnOrSuperColumn zipCosc = new ColumnOrSuperColumn();
		zipCosc.column = zipCol;
		
		Mutation nameMut = new Mutation();
		nameMut.column_or_supercolumn = nameCosc;
		Mutation phoneMut = new Mutation();
		phoneMut.column_or_supercolumn = phoneCosc;
		Mutation addressMut = new Mutation();
		addressMut.column_or_supercolumn = addressCosc;
		Mutation cityMut = new Mutation();
		cityMut.column_or_supercolumn = cityCosc;
		Mutation stateMut = new Mutation();
		stateMut.column_or_supercolumn = stateCosc;
		Mutation zipMut = new Mutation();
		zipMut.column_or_supercolumn = zipCosc;

		//set up the batch
		Map<byte[], Map<String, List<Mutation>>> mutationMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		
		Map<String, List<Mutation>> muts = 
			new HashMap<String, List<Mutation>>();
		List<Mutation> cols = new ArrayList<Mutation>();
		cols.add(nameMut);
		cols.add(phoneMut);
		cols.add(addressMut);
		cols.add(cityMut);
		cols.add(stateMut);
		cols.add(zipMut);

		muts.put(columnFamily, cols);
		
		//outer map key is a row key
		//inner map key is the column family name
		mutationMap.put(rowKey.getBytes(), muts);
		return mutationMap;
	}

	//add Waldorf hotel to Hotel CF
	private Map<byte[], Map<String, List<Mutation>>> createWaldorfMutation(
			String columnFamily, String rowKey)
			throws UnsupportedEncodingException {
		
		Clock clock = new Clock(System.nanoTime());
		
		Column nameCol = new Column("name".getBytes(UTF8), 
				WALDORF_NAME.getBytes("UTF-8"), clock);
		Column phoneCol = new Column("phone".getBytes(UTF8),
				"212-555-5555".getBytes(UTF8), clock);
		Column addressCol = new Column("address".getBytes(UTF8), 
				"301 Park Ave".getBytes(UTF8), clock);
		Column cityCol = new Column("city".getBytes(UTF8),
				"New York".getBytes(UTF8), clock);
		Column stateCol = new Column("state".getBytes(UTF8), 
				"NY".getBytes("UTF-8"), clock);
		Column zipCol = new Column("zip".getBytes(UTF8), 
				"10019".getBytes(UTF8), clock);
		
		ColumnOrSuperColumn nameCosc = new ColumnOrSuperColumn();
		nameCosc.column = nameCol;
		
		ColumnOrSuperColumn phoneCosc = new ColumnOrSuperColumn();
		phoneCosc.column = phoneCol;
		
		ColumnOrSuperColumn addressCosc = new ColumnOrSuperColumn();
		addressCosc.column = addressCol;

		ColumnOrSuperColumn cityCosc = new ColumnOrSuperColumn();
		cityCosc.column = cityCol;

		ColumnOrSuperColumn stateCosc = new ColumnOrSuperColumn();
		stateCosc.column = stateCol;
		
		ColumnOrSuperColumn zipCosc = new ColumnOrSuperColumn();
		zipCosc.column = zipCol;
		
		Mutation nameMut = new Mutation();
		nameMut.column_or_supercolumn = nameCosc;
		Mutation phoneMut = new Mutation();
		phoneMut.column_or_supercolumn = phoneCosc;
		Mutation addressMut = new Mutation();
		addressMut.column_or_supercolumn = addressCosc;
		Mutation cityMut = new Mutation();
		cityMut.column_or_supercolumn = cityCosc;
		Mutation stateMut = new Mutation();
		stateMut.column_or_supercolumn = stateCosc;
		Mutation zipMut = new Mutation();
		zipMut.column_or_supercolumn = zipCosc;

		//set up the batch
		Map<byte[], Map<String, List<Mutation>>> mutationMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		
		Map<String, List<Mutation>> muts = 
			new HashMap<String, List<Mutation>>();
		List<Mutation> cols = new ArrayList<Mutation>();
		cols.add(nameMut);
		cols.add(phoneMut);
		cols.add(addressMut);
		cols.add(cityMut);
		cols.add(stateMut);
		cols.add(zipMut);

		muts.put(columnFamily, cols);
		
		//outer map key is a row key
		//inner map key is the column family name
		mutationMap.put(rowKey.getBytes(), muts);
		return mutationMap;
	}
	
	//set up columns to insert for Clarion
 private Map<byte[], Map<String, List<Mutation>>> createClarionMutation(
			String columnFamily, String rowKey)
			throws UnsupportedEncodingException {
		
	 	Clock clock = new Clock(System.nanoTime());
		
		Column nameCol = new Column("name".getBytes(UTF8), 
				CLARION_NAME.getBytes("UTF-8"), clock);
		Column phoneCol = new Column("phone".getBytes(UTF8),
				"480-333-3333".getBytes(UTF8), clock);
		Column addressCol = new Column("address".getBytes(UTF8), 
				"3000 N. Scottsdale Rd".getBytes(UTF8), clock);
		Column cityCol = new Column("city".getBytes(UTF8),
				"Scottsdale".getBytes(UTF8), clock);
		Column stateCol = new Column("state".getBytes(UTF8), 
				"AZ".getBytes("UTF-8"), clock);
		Column zipCol = new Column("zip".getBytes(UTF8), 
				"85255".getBytes(UTF8), clock);
		
		ColumnOrSuperColumn nameCosc = new ColumnOrSuperColumn();
		nameCosc.column = nameCol;
		
		ColumnOrSuperColumn phoneCosc = new ColumnOrSuperColumn();
		phoneCosc.column = phoneCol;
		
		ColumnOrSuperColumn addressCosc = new ColumnOrSuperColumn();
		addressCosc.column = addressCol;

		ColumnOrSuperColumn cityCosc = new ColumnOrSuperColumn();
		cityCosc.column = cityCol;

		ColumnOrSuperColumn stateCosc = new ColumnOrSuperColumn();
		stateCosc.column = stateCol;
		
		ColumnOrSuperColumn zipCosc = new ColumnOrSuperColumn();
		zipCosc.column = zipCol;
		
		Mutation nameMut = new Mutation();
		nameMut.column_or_supercolumn = nameCosc;
		Mutation phoneMut = new Mutation();
		phoneMut.column_or_supercolumn = phoneCosc;
		Mutation addressMut = new Mutation();
		addressMut.column_or_supercolumn = addressCosc;
		Mutation cityMut = new Mutation();
		cityMut.column_or_supercolumn = cityCosc;
		Mutation stateMut = new Mutation();
		stateMut.column_or_supercolumn = stateCosc;
		Mutation zipMut = new Mutation();
		zipMut.column_or_supercolumn = zipCosc;

		//set up the batch
		Map<byte[], Map<String, List<Mutation>>> mutationMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
		
		Map<String, List<Mutation>> muts = 
			new HashMap<String, List<Mutation>>();
		List<Mutation> cols = new ArrayList<Mutation>();
		cols.add(nameMut);
		cols.add(phoneMut);
		cols.add(addressMut);
		cols.add(cityMut);
		cols.add(stateMut);
		cols.add(zipMut);

		muts.put(columnFamily, cols);
		
		//outer map key is a row key
		//inner map key is the column family name
		mutationMap.put(rowKey.getBytes(), muts);
		return mutationMap;
	}

	//set up columns to insert for Cambria
	private Map<byte[], Map<String, List<Mutation>>> createCambriaMutation(
			String columnFamily, String cambriaKey)
			throws UnsupportedEncodingException {
		
		//set up columns for Cambria
		Clock clock = new Clock(System.nanoTime());
		
		Column cambriaNameCol = new Column("name".getBytes(UTF8), 
				"Cambria Suites Hayden".getBytes("UTF-8"), clock);
		Column cambriaPhoneCol = new Column("phone".getBytes(UTF8),
				"480-444-4444".getBytes(UTF8), clock);
		Column cambriaAddressCol = new Column("address".getBytes(UTF8), 
				"400 N. Hayden".getBytes(UTF8), clock);
		Column cambriaCityCol = new Column("city".getBytes(UTF8),
				"Scottsdale".getBytes(UTF8), clock);
		Column cambriaStateCol = new Column("state".getBytes(UTF8), 
				"AZ".getBytes("UTF-8"), clock);
		Column cambriaZipCol = new Column("zip".getBytes(UTF8), 
				"85255".getBytes(UTF8), clock);
		
		ColumnOrSuperColumn nameCosc = new ColumnOrSuperColumn();
		nameCosc.column = cambriaNameCol;
		
		ColumnOrSuperColumn phoneCosc = new ColumnOrSuperColumn();
		phoneCosc.column = cambriaPhoneCol;
		
		ColumnOrSuperColumn addressCosc = new ColumnOrSuperColumn();
		addressCosc.column = cambriaAddressCol;

		ColumnOrSuperColumn cityCosc = new ColumnOrSuperColumn();
		cityCosc.column = cambriaCityCol;

		ColumnOrSuperColumn stateCosc = new ColumnOrSuperColumn();
		stateCosc.column = cambriaStateCol;
		
		ColumnOrSuperColumn zipCosc = new ColumnOrSuperColumn();
		zipCosc.column = cambriaZipCol;
		
		Mutation nameMut = new Mutation();
		nameMut.column_or_supercolumn = nameCosc;
		Mutation phoneMut = new Mutation();
		phoneMut.column_or_supercolumn = phoneCosc;
		Mutation addressMut = new Mutation();
		addressMut.column_or_supercolumn = addressCosc;
		Mutation cityMut = new Mutation();
		cityMut.column_or_supercolumn = cityCosc;
		Mutation stateMut = new Mutation();
		stateMut.column_or_supercolumn = stateCosc;
		Mutation zipMut = new Mutation();
		zipMut.column_or_supercolumn = zipCosc;

		//set up the batch
		Map<byte[], Map<String, List<Mutation>>> cambriaMutationMap = 
			new HashMap<byte[], Map<String, List<Mutation>>>();
			
		Map<String, List<Mutation>> cambriaMuts = 
			new HashMap<String, List<Mutation>>();
		List<Mutation> cambriaCols = new ArrayList<Mutation>();
		cambriaCols.add(nameMut);
		cambriaCols.add(phoneMut);
		cambriaCols.add(addressMut);
		cambriaCols.add(cityMut);
		cambriaCols.add(stateMut);
		cambriaCols.add(zipMut);

		cambriaMuts.put(columnFamily, cambriaCols);
		
		//outer map key is a row key
		//inner map key is the column family name
		cambriaMutationMap.put(cambriaKey.getBytes(), cambriaMuts);
		return cambriaMutationMap;
	}
}

This is a rather long example, but it attempts to show something more than “hello, world”—there are a number of insert and batch_mutate operations shown with standard column families and super column families. I have also included multiple rows for each type so that more sophisticated queries are required.

This class is the first to execute in our sample application, and once the prepopulate method is complete, your database will have all the data that the search functions need to work with.

The Search Application

Example 4-7 is the Java class with the main method that you should execute. It relies on Log4J, so you’ll want to point to your log4j.properties file when you run it. All you have to do is run this class, and the database gets prepopulated with all of the hotel and point of interest information; then, it allows the user to search for hotels in a given city. The user picks one hotel, and the application fetches the nearby points of interest. You can then implement the remaining parts of the application to book a reservation if you like.

Example 4-7. HotelApp.java

package com.cassandraguide.hotel;

import static com.cassandraguide.hotel.Constants.CL;
import static com.cassandraguide.hotel.Constants.UTF8;

import java.util.ArrayList;
import java.util.List;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.KeyRange;
import org.apache.cassandra.thrift.KeySlice;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.apache.cassandra.thrift.SuperColumn;
import org.apache.log4j.Logger;

/**
 * Runs the hotel application. After the database is pre-populated,
 * this class mocks a user interaction to perform a hotel search based on 
 * city, selects one, then looks at some surrounding points of interest for 
 * that hotel. 
 * 
 * Shows using Materialized View pattern, get, get_range_slices, key slices.
 * 
 * These exceptions are thrown out of main to reduce code size:
 * UnsupportedEncodingException,
   InvalidRequestException, UnavailableException, TimedOutException,
   TException, NotFoundException, InterruptedException
   
   Uses the Constants class for some commonly used strings.
 */
public class HotelApp {
	private static final Logger LOG = Logger.getLogger(HotelApp.class);

	public static void main(String[] args) throws Exception {

		//first put all of the data in the database
		new Prepopulate().prepopulate();
		LOG.debug("** Database filled. **");
		
		//now run our client
		LOG.debug("** Starting hotel reservation app. **");
		HotelApp app = new HotelApp();

		//find a hotel by city--try Scottsdale or New York...
		List<Hotel> hotels = app.findHotelByCity("Scottsdale", "AZ");
		//List<Hotel> hotels = app.findHotelByCity("New York", "NY");
		LOG.debug("Found hotels in city. Results: " + hotels.size());

		//choose one
		Hotel h = hotels.get(0);

		LOG.debug("You picked " + h.name);
		
		//find Points of Interest for selected hotel
		LOG.debug("Finding Points of Interest near " + h.name);
		List<POI> points = app.findPOIByHotel(h.name);

		//choose one
		POI poi = points.get(0);
		LOG.debug("Hm... " + poi.name + ". " + poi.desc + "--Sounds fun!");

		LOG.debug("Now to book a room...");
		
		//show availability for a date
		//left as an exercise...

		//create reservation
		//left as an exercise...

		LOG.debug("All done.");
	}

	//use column slice to get from Super Column
	public List<POI> findPOIByHotel(String hotel) throws Exception {

		///query
		SlicePredicate predicate = new SlicePredicate();
		SliceRange sliceRange = new SliceRange();
		sliceRange.setStart(hotel.getBytes());
		sliceRange.setFinish(hotel.getBytes());
		predicate.setSlice_range(sliceRange);

		// read all columns in the row
		String scFamily = "PointOfInterest";
		ColumnParent parent = new ColumnParent(scFamily);

		KeyRange keyRange = new KeyRange();
		keyRange.start_key = "".getBytes();
		keyRange.end_key = "".getBytes();

		List<POI> pois = new ArrayList<POI>();

		//instead of a simple list, we get a map whose keys are row keys
		//and the values the list of columns returned for each
		//only row key + first column are indexed
		Connector cl = new Connector();
		Cassandra.Client client = cl.connect();
		List<KeySlice> slices = client.get_range_slices(
				parent, predicate, keyRange, CL);

		for (KeySlice slice : slices) {	
			List<ColumnOrSuperColumn> cols = slice.columns;

			POI poi = new POI();
			poi.name = new String(slice.key);

			for (ColumnOrSuperColumn cosc : cols) {
				SuperColumn sc = cosc.super_column;

				List<Column> colsInSc = sc.columns;

				for (Column c : colsInSc) {
					String colName = new String(c.name, UTF8);
					if (colName.equals("desc")) {
						poi.desc = new String(c.value, UTF8);
					}
					if (colName.equals("phone")) {
						poi.phone = new String(c.value, UTF8);
					}
				}

				LOG.debug("Found something neat nearby: " + poi.name + 
						". \nDesc: " + poi.desc + 
						". \nPhone: " + poi.phone);
				pois.add(poi);
			}
		}

		cl.close();
		return pois;
	}

	//uses key range
	public List<Hotel> findHotelByCity(String city, String state) 
		throws Exception {
		
		LOG.debug("Seaching for hotels in " + city + ", " + state);

		String key = city + ":" + state.toUpperCase();

		///query
		SlicePredicate predicate = new SlicePredicate();
		SliceRange sliceRange = new SliceRange();
		sliceRange.setStart(new byte[0]);
		sliceRange.setFinish(new byte[0]);
		predicate.setSlice_range(sliceRange);

		// read all columns in the row
		String columnFamily = "HotelByCity";
		ColumnParent parent = new ColumnParent(columnFamily);

		KeyRange keyRange = new KeyRange();
		keyRange.setStart_key(key.getBytes());
		keyRange.setEnd_key((key+1).getBytes()); //just outside lexical range
		keyRange.count = 5;

		Connector cl = new Connector();
		Cassandra.Client client = cl.connect();
		List<KeySlice> keySlices = 
			client.get_range_slices(parent, predicate, keyRange, CL);

		List<Hotel> results = new ArrayList<Hotel>();

		for (KeySlice ks : keySlices) {
			List<ColumnOrSuperColumn> coscs = ks.columns;
			LOG.debug(new String("Using key " + ks.key));

			for (ColumnOrSuperColumn cs : coscs) {			

				Hotel hotel = new Hotel();
				hotel.name = new String(cs.column.name, UTF8);
				hotel.city = city;
				hotel.state = state;

				results.add(hotel);
				LOG.debug("Found hotel result for " + hotel.name);
			}
		}
		///end query
		cl.close();

		return results;
	}
}

I interspersed the code with comments to illustrate the purpose of the different statements.

The output of running the application is shown in Example 4-8.

Example 4-8. Output of running the hotel application

DEBUG 09:49:50,858 Inserted AZC_043
DEBUG 09:49:50,861 Inserted AZS_011
DEBUG 09:49:50,863 Inserted CAS_021
DEBUG 09:49:50,864 Inserted NYN_042
DEBUG 09:49:50,864 Done inserting at 6902368219815217
DEBUG 09:49:50,873 Inserted HotelByCity index for Cambria Suites Hayden
DEBUG 09:49:50,874 Inserted HotelByCity index for Clarion Scottsdale Peak
DEBUG 09:49:50,875 Inserted HotelByCity index for The W SF
DEBUG 09:49:50,877 Inserted HotelByCity index for The Waldorf=Astoria
DEBUG 09:49:50,877 Inserting POIs.
DEBUG 09:49:50,880 Done inserting Empire State.
DEBUG 09:49:50,881 Done inserting Central Park.
DEBUG 09:49:50,885 Done inserting Phoenix Zoo.
DEBUG 09:49:50,887 Done inserting Spring Training.
DEBUG 09:49:50,887 Done inserting POIs.
DEBUG 09:49:50,887 ** Database filled. **
DEBUG 09:49:50,889 ** Starting hotel reservation app. **
DEBUG 09:49:50,889 Seaching for hotels in Scottsdale, AZ
DEBUG 09:49:50,902 Using key [B@15e9756
DEBUG 09:49:50,903 Found hotel result for Cambria Suites Hayden
DEBUG 09:49:50,903 Found hotel result for Clarion Scottsdale Peak
DEBUG 09:49:50,904 Found hotels in city. Results: 2
DEBUG 09:49:50,904 You picked Cambria Suites Hayden
DEBUG 09:49:50,904 Finding Points of Interest near Cambria Suites Hayden
DEBUG 09:49:50,911 Found something neat nearby: Phoenix Zoo. 
Desc: They have animals here.. 
Phone: 480-555-9999
DEBUG 09:49:50,911 Found something neat nearby: Spring Training. 
Desc: Fun for baseball fans.. 
Phone: 623-333-3333
DEBUG 09:49:50,911 Hm... Phoenix Zoo. They have animals here.--Sounds fun!
DEBUG 09:49:50,911 Now to book a room...
DEBUG 09:49:50,912 All done.

Again, you typically don’t want to write against Thrift or Avro yourself, but instead should use one of the clients listed in Chapter 8. The purpose here is to give you an idea of how the plumbing works, and to show a complete, working application that performs inserts and various searches and that resembles real-world usage.

Twissandra

When you start thinking about how to design for Cassandra, take a look at Twissandra, written by Eric Florenzano. Visit http://www.twissandra.com to see a fully working Twitter clone that you can download and try out. The source is all in Python, and it has a few dependencies on Django and a JSON library to sort out, but it’s a great place to start. You can use what’s likely a familiar data model (Twitter’s) and see how users, time lines, and tweets all fit into a simple Cassandra data model.

There is also a helpful post by Eric Evans explaining how to use Twissandra, which is available at http://www.rackspacecloud.com/blog/2010/05/12/cassandra-by-example.

Summary

In this chapter we saw how to create a complete, working Cassandra application. We also illustrated a typical relational model compared with how you might represent that model in Cassandra, and introduced a variety of commands for interacting with the database.

The best content for your career. Discover unlimited learning on demand for around $1/day.