2011년 6월 26일 일요일

Cassandra (4/12)

4. Sample 애플리케이션

이 장에서는 완전한 샘플 애플리케이션을 만들어서 모든 파트가 잘 맞는 것을 볼것이다. 데이터를 입력하고, 배치 업데이트를 실행하고 컬럼군과 super 컬럼군을 검색하는 등 API의 다양한 면을 실행하여 어떻게 동작하는지도 볼 것이다.
예제를 만들기 위하여, 다양한 데이터 구조와 기본 API의 동작들을 보여줄 만큼 복잡한 것들을 사용하기 원한다. 하지만 너무 자세한 것까지 다루어 당신을 지치게 하지는 않을 것이다. 데이터베이스에서 충분한 데이터를 갖기 위해서 우리의 검색을 제대로 돌아가게 할것이다. 데이터베이스의 밀도 때문에 거기는 조금 중복이 있을 것이다. 그리고 또 나는 카산드라가 어떻게 동작하는지에 집중할 수 있도록 익숙한 도메인을 사용해서 애플리케이션 도메인에 관해서 신경쓰지 않도록 할 것이다.

4.1. 데이터 디자인

당신이 관계형 데이터베이스를 사용하는 새 데이터 드리븐 애플리케이션을 빌드하려고 할 때, 당신은 적당히 정규화된 테이블과 같이 도메인을 모델링하거나 다른 테이블의 관계형 데이터를 참조하기위해 외래 키를 사용함으로써 시작할 수 있다. 이제 카산드라가 어떻게 데이터를 저장하는지 알므로 관계형 세상에서도 이해하기 쉬운 작은 도메인 모델을 만든다. 그리고 카산드라에서 관계형에서 분산 해쉬테이블 모델로 어떻게 매핑하는지 본다.
관계형 모델링은 간단히 말해서 당신이 개념적인 도메인에서 시작해서 도메인에 있는 명사들을 테이블에 표현하는 것이다. 당신은 primary key와 외래키를 모델 관계에 할당한다. 당신이 다대다 관계를 가졌을 때 그 키들을 표현할 join table을 만든다. Join table은 실제 세계에는 존재하지 않는다. 그리고 관계형 모델들이 작동하는데 필요한 영향을 미친다. 당신이 모든 테이블들을 만들어 놓은 후에 키에 의해 정의된 관계를 사용하는 흩어진 데이터를 소집하는 쿼리를 쓸 수 있다. 관계형 세상에서의 쿼리는 부차적이다. 테이블이 옳게 모델이 되어있다면 당신이 원하는 데이터를 언제나 가져올 수 있다고 가정한다. 당신이 여러 개의 복잡한 서브쿼리나 join statement를 사용해도 이것은 진실이다.
반면에 카산드라에서 당신은 데이터 모델로부터 시작하지는 않고 쿼리 모델로부터 시작한다.
이 예를 위해 쉽게 이해되고 모두 관계될 수 있는 도메인을 사용해보자. 어떤 호텔이 있고 손님이 예약을 할 수 있도록 하는 예이다.
우리의 개념적인 도메인은 호텔과 거기에 묵는 손님, 각 호텔의 방들, 어떤 방에 어떤 손님이 얼마기간 동안 묵는다는 예약 기록 등이있다. 호텔은 일반적으로 흥미있는 지역을 모아 유지하고 있다. 이것은 공원, 박물관, 쇼핑 갤러리, 기념물, 다른 장소 등 호텔 근처의 손님들이 그들이 머무는 동안 방문해 볼 만한 곳이다. 호텔과 이 장소들은 모두 지정학 위치 데이터를 가지고 지도 상에서 매쉬업, 거리 측정등을 통해 발견되기를 바란다.
여기서 카산드라의 애플리케이션 디자인을 해보겠다. 첫째, 당신의 쿼리를 정한다. 아래와 같은 것들이 있을 것이다.
 주어진 지역의 호텔을 찾는다.
 이름, 위치 등 호텔에 대한 정보를 찾는다.
 주어진 호텔 근처에 흥미로운 장소를 찾는다.
 생각하고 있는 기간 동안에 사용가능한 방이 있는지 찾는다.
 방의 가격과 편의 시설 등을 찾는다.
 손님 정보란에 들어가서 선택한 방을 예약한다.

4.2. 호텔 애플리케이션 RDBMS 디자인
그림 4-1은 우리가 어떻게 간단히 호텔 예약 시스템을 관계형 데이터베이스 모델을 사용하여 만들수 있는지 보여준다. 관계형 모델은 몇 개의 “join” 테이블을 포함한다. 이는 다대다 관계들 해결하기 위함인데 이는 호텔 대 흥미로운 장소, 방 대 편의 시설 등이다.

그림4-1. RDBMS를 사용한 간단한 호텔 검색 시스템

4.3. 호텔 애플리케이션 카산드라 디자인

여러가지 방법이 있겠지만, 우리는 여기서 그림 4-2에 보여진 피지컬한 카산드라 모델을 사용하여 논리적인 데이터 모델을 나타내 본다.
이 디자인에서 우리는 관계형 디자인에서 했던것처럼 해본다. 호텔, 손님 같은 테이블들을 컬럼군으로 변환해본다. PointOfInterest 같은 다른 테이블은 super 컬럼군으로 비정규화되었다. 관계형 모델에서 SQL 문장을 이용해서 도시 이름을 사용하여 호텔을 찾아볼 수 있다. 그러나 카산드라에 SQL 없기 때문에 HotelByCity 컬럼군 형태로 인덱스를 만들었다.
우리는 방과 편의시설을 합쳐서 Room이라는 하나의 컬럼군으로 만들었다. 타입, 요금 등의 컬럼은 해당하는 값을 가지고 있다. 뜨거운 욕조 등의 컬럼은 단지 컬럼이름 자체의 존재만을 사용할 것이며, 안그러면 비어있다.

그림4-2. 카산드라 모델로 표현된 호텔 검색

4.4. 호텔 애플리케이션 코드
이 섹션에서는 우리는 코드를 살펴보고 주어진 디자인을 어떻게 구현할지 본다. 여러가지 다른 API의 동작을 보여주기 때문에 이것은 유용하다.
우리가 만드는 애플리케이션은 아래와 같은 일을 수행할 것이다.
1. 데이터베이스 구조를 만든다.
2. 호텔과 흥미로운 장소 등의 데이터를 가지고 데이터베이스를 미리 만든다. 호텔은 표준 컬럼군에 저장이 될 것이고 다른 흥미로운 장소는 super 컬럼군에 저장된다.
3. 주어진 도시에서 호텔의 리스트를 검색한다. 이것은 두번째 인덱스를 사용한다.
4. 검색에서 반환된 호텔을 고른다. 그리고 고른 호텔 주변의 흥미로운 장소의 리스트를 검색한다.
5. Reservation 컬럼에 삽입 동작을 해서 예약을 한다.
모든 애플리케이션을 구현하기에는 공간이 모자란다. 그러나 주된 부분만 하고 나머지는 같은 것의 반복일 뿐인 구현은 놔둔다.

4.5. 데이터베이스 만들기

첫번째 단계는 스키마 정의를 만드는것이다. 이 예제에서 우리는 클라이언트 코드를 정의하기 위해 사용할 수도 있지만 스키마를 YAML로 정의하고 로드한다.
YAML 파일은 아래 Example 4-1에 보여지며 필요한 키공간과 컬럼군을 정의했다.

Example 4-1. 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

이 정의는 예제를 작동하기 위한 모든 컬럼군을 제공한다. 그리고 RDBMS에서 변환되었기 때문에 애플리케이션 코드에서 직접적으로 참조하지는 않는것도 있다.

4.5.1. 스키마 로딩하기

YAML에 스키마가 정의되면 로드를 해야한다. 이것을 하기위해서는 console을 열고 jconsole 애플리케이션을 실행한다. 그리고 카산드라에 JMX를 통해 연결한다. 그리고 loadSchemaFromYAML 이라는 동작을 실행한다. 이것은 org.apache.cassandra.service.StorageService MBean 의 일부이다. 이제 카산드라는 당신의 스키마를 알고 그것을 사용하기 시작한다. 당신은 또한 API를 사용하고 키스페이스와 컬럼군을 만들수 있다.

4.6. 데이터 구조

애플리케이션은 단지 변환 오브젝트처럼 우리를 위해 작동할 표준 데이터 구조를 필요로 한다. 이것은 특별히 흥미롭지는 않지만 이것저것이 잘 정돈되어 있기 위해 필요하다. Hotel 데이터 구조를 사용하여 모든 호텔관련 정보를 보관하여 다음에 보여준다.
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;
}

이 구조는 애플리케이션의 편리함을 위하여 컬럼 정보만을 가지고 있다.
흥미로운 장소 정보를 갖기 위하여 POI 데이터 구조도 가지고 있다. 다음에 보여준다.
package com.cassandraguide.hotel;
//data transfer object for a Point of Interest
public class POI {
public String name;
public String desc;
public String phone;
}

Constants 클래스도 가지고 있고 이는 변경하기 쉬운 장소에 공통으로 사용되는 스트링을 보관한다. 다음에 보여준다.
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;
}

공통 사용 스트링을 이렇게 보관하는 것은 코드를 더 깨끗하고 간결하게 해주고 당신의 환경에서 의미가 있도록 값들을 변경하는 것이 쉽게 한다.

4.7. 커넥션 맺기

우리는 너무 많이 쓸데없이 반복하는 것을 피하고 편리를 위해 커넥션 코드를 한 클래스에 넣고 이를 Connector라고 부른다. 다음에 보여준다.
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();
}
}

데이터 베이스 동작을 수행할 필요가 있을 때 우리는 커넥션을 맺고 끊기 위해서 이 클래스를 사용할 수 있다.

4.8. 데이터베이스 만들기

Prepopulate 클래스는 다음에 보여지며 사용자가 검색할 호텔, 흥미로운 장소 정보를 데이터베이스에 미리 만들기위해서 insert, batch_mutates 등을 수행한다.
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


List cols = new ArrayList();
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 {


List columnsToAdd = new ArrayList();
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 cols = new ArrayList();
cols.add(descCol);
cols.add(phoneCol);

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 {

List columnsToAdd = new ArrayList();
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 cols = new ArrayList();
cols.add(descCol);
cols.add(phoneCol);

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 {

List columnsToAdd = new ArrayList();
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 cols = new ArrayList();
cols.add(descCol);

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 {

List columnsToAdd = new ArrayList();
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 esbCols = new ArrayList();
esbCols.add(descCol);
esbCols.add(phoneCol);

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

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

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

List cols = new ArrayList();
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
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

List cols = new ArrayList();
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
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

List cols = new ArrayList();
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
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

List cambriaCols = new ArrayList();
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;
}
}

이것은 꽤 긴 예제이지만 “hello world” 보다는 더 많은 걸 보여주려고 노력한다. 많은 수의 insert와 batch_mutate 동작이 있다. 이것은 표준 컬럼군과 super 컬럼군이다. 또한 많은 수의 행을 포함하여 정교한 쿼리가 필요하도록 했다.
이 클래스는 우리의 예제를 실행하기 위한 첫번째이다. Prepopulate 방법이 완료되면 당신의 데이버베이스는 검색을 수행하기 위한 모든 데이터를 갖게 된다.

4.9. 검색 애플리케이션

다음은 main 메소드를 가진 자바 클래스이며 당신이 수행해야 한다. Log4J에 의존하기 때문에 당신의 log4j.properties 파일에 맞추어 실행할 것이다. 당신이 할 일은 이 클래스를 실행하는것 뿐이며, 그러면 자동으로 모든 호텔과 흥미로운 장소 정보를 만들고 사용자가 주어진 도시에 대해서 검색할 수 있도록 해준다. 사용자는 한 호텔을 택하고 애플리케이션은 주변의 흥미로운 장소를 검색한다. 그리고 당신은 애플리케이션의 나머지 부분을 구현하여 예약을 마칠 수 있다.
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 hotels = app.findHotelByCity("Scottsdale", "AZ");
//List 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 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 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 pois = new ArrayList();
//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 slices = client.get_range_slices(
parent, predicate, keyRange, CL);
for (KeySlice slice : slices) {
List cols = slice.columns;
POI poi = new POI();
poi.name = new String(slice.key);
for (ColumnOrSuperColumn cosc : cols) {
SuperColumn sc = cosc.super_column;
List 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 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 keySlices =
client.get_range_slices(parent, predicate, keyRange, CL);
List results = new ArrayList();
for (KeySlice ks : keySlices) {
List 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;
}
}

각기 다른 문장들의 의도를 설명하기 위해 곳곳에 커멘트를 배치하였다.
애플리케이션을 실행시킨 결과를 다음에 보여준다.
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.

다시한 번 당신은 Thrift나 Avro에 반해서 쓰기를 원치 않아서 대신 8장에 리스트된 클라이언트를 사용하게 될 것이다. 여기서 목적은 당신이 이것이 어떻게 작동하는지 보여주고 완전한 작동 애플리케이션을 보여주어 insert 와 많은 검색들이 일하는 것을 보고 실제로 어떻게 작동하는지 보여주는 것이다.

4.10. Twissandra

당신이 카산드라를 어떻게 디자인하는지 궁금해하기 시작할 때 Eric Florenzano에 의해 쓰여진 Twissandra를 살펴보자. http://www.twissandra.com을 방문하여 다운로드하여 써볼수 있는 트위터 클론을 보자. 소스는 Phthon 이며 정렬하기 위해서 Django와 JSON 라이브러리에 좀 의존하고 있지만 시작하기 좋은 장소이다. 당신은 트위터와 같은 익숙한 데이터 모델을 사용할 수 있고 사용자, 타임라인, 트윗 등이 간단한 카산드라 데이터 모델에 작동하는 것을 볼 수 있다.
Eric Evans의 Twitssandra를 사용하는데 필요한 글도 있는데, 그것은 http://www.rackspacecloud.com/blog/2010/05/12/cassandra-by-example 이다.

댓글 없음:

댓글 쓰기