Vídeo da palestra NoSQL: onde, como e por quê? Cassandra e MongoDB.
Information technology and tangential subjects
Vídeo da palestra NoSQL: onde, como e por quê? Cassandra e MongoDB.
Segue material da apresentação que fiz no dia 10/11 no Seminário de Gerenciamento de Dados em Software Livre no SERPRO.
Visão geral sobre bancos de dados NoSQL e detalhes técnicos dos modelos de dados e arquiteturas das implementações Apache Cassandra e MongoDB.
Aplicações modernas provaram que bancos de dados do tipo NoSQL são inevitáveis para o sucesso e continuidade de empresas altamente dependentes da Internet. Vide exemplos como Google, Yahoo, Amazon, Twitter e Facebook.
Todavia, existem inúmeras soluções disponíveis e nenhum padrão sobre como manipular, trafegar ou consultar as informações contidas nos bancos NoSQL. Mesmo a classificação (ou melhor dizer, a taxonomia) dessa zoologia de novos bancos ainda está (perdoem-me o trocadilho!) nebulosa... Quem sabe futuramente tenhamos um ANSI-NoSQL...
O fato é que algumas dessas tecnologias provaram ser apenas estufas para o meio acadêmico, enquanto que outras chegaram a evoluir a ponto de serem aceitas por empresas que apostam no pioneirismo. Uma dessas tecnologias de sucesso foi o MongoDB.
Nesta apresentação são introduzidos conceitos como as Grandes Rupturas (IMS x RDBMS x NoSQL), o que é o MongoDB, o Modelo de Dados Orientado a Documentos, JSON e BSON, tipos de dados no MongoDB, operações (Insert, Update, Delete), Modificadores Atômicos, Linguagem de Consulta, Indexação, Agregação e Map/Reduce, Capped Collections, GridFS, Server-Side Scripting, Replicação (Master/Slave e Replica Sets), Arquitetura com Sharding, Auto-Sharding + Replicação e outras tecnologias e detalhes envolvidos no banco de dados MongoDB.
Um termo recorrente quando se fala em computação em nuvem é a persistência dos dados em bancos do tipo NoSQL, ou seja, em uma forma não-relacional. Essa tecnologia não substitui os consolidados Sistemas Gerenciadores de Bancos de Dados Relacionais (SGBDRs), mas ao invés disso torna-se uma nova ferramenta disponível ao desenvolvedor.
Implantar uma aplicação na nuvem não significa que NoSQL será utilizada. Entretanto, as funcionalidades que essa tecnologia provê são altamente convergentes com as propostas da computação em nuvem: performance, escalabilidade horizontal, alta disponibilidade e flexibilidade.
Nesta apresentação são introduzidos conceitos como Computação em Nuvem, Persistência de Dados, Bancos de Dados Relacionais, o movimento NoSQL, o modelo de dados do Bigtable da Google, a arquitetura do Dynamo da Amazon e detalhes técnicos do Apache Cassandra.
/opt/apache-cassandra-0.6.5/nodes
|
|-- node1
| |-- bin
| |-- conf
| |-- data
| |-- log
| `-- txs
|
|-- node2
| |-- bin
| |-- conf
| |-- data
| |-- log
| `-- txs
|
`-- node3
|-- bin
|-- conf
|-- data
|-- log
`-- txs
# ifconfig lo:2 127.0.0.2 up # ifconfig lo:3 127.0.0.3 up
$ ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:30848 errors:0 dropped:0 overruns:0 frame:0
TX packets:30848 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2946793 (2.9 MB) TX bytes:2946793 (2.9 MB)
lo:2 Link encap:Local Loopback
inet addr:127.0.0.2 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
lo:3 Link encap:Local Loopback
inet addr:127.0.0.3 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
/etc/hosts: 127.0.0.1 localhost node1 127.0.0.2 node2 127.0.0.3 node3
$ ping node2 PING node2 (127.0.0.2) 56(84) bytes of data. 64 bytes from node2 (127.0.0.2): icmp_seq=1 ttl=64 time=0.018 ms 64 bytes from node2 (127.0.0.2): icmp_seq=2 ttl=64 time=0.015 ms ^C --- node2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.015/0.016/0.018/0.004 ms
$ cd /opt/apache-cassandra-0.6.5/ $ mkdir -p nodes/node1 $ cp -R bin/ conf/ nodes/node1/ $ cd nodes/node1
# Edit the next line to point to your logs directory log4j.appender.R.File=./log/system.log
<Keyspaces>
<Keyspace Name="Keyspace1">
...
<ReplicationFactor>2</ReplicationFactor>
...
</Keyspace>
<Keyspaces>
...
<CommitLogDirectory>./txs</CommitLogDirectory>
<DataFileDirectories>
<DataFileDirectory>./data</DataFileDirectory>
</DataFileDirectories>
...
<ListenAddress>node1</ListenAddress>
<StoragePort>7000</StoragePort>
...
<ThriftAddress></ThriftAddress>
<ThriftPort>9160</ThriftPort>
<ThriftFramedTransport>false</ThriftFramedTransport>
...
for jar in $cassandra_home/../../lib/*.jar; do
CLASSPATH=$CLASSPATH:$jar
done
$ cd /opt/apache-cassandra-0.6.5/nodes/ $ mkdir node2 node3 $ cp -R node1/* node2 $ cp -R node1/* node3
$ tree -L 2
.
|-- node1
| |-- bin
| `-- conf
|-- node2
| |-- bin
| `-- conf
`-- node3
|-- bin
`-- conf
<ListenAddress>node2</ListenAddress>
# Arguments to pass to the JVM
JVM_OPTS=" \
-ea \
-Xms1G \
-Xmx1G \
-XX:+UseParNewGC \
-XX:+UseConcMarkSweepGC \
-XX:+CMSParallelRemarkEnabled \
-XX:SurvivorRatio=8 \
-XX:MaxTenuringThreshold=1 \
-XX:+HeapDumpOnOutOfMemoryError \
-Dcom.sun.management.jmxremote.port=8082 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false"
$ node1/bin/cassandra -f $ node2/bin/cassandra -f $ node3/bin/cassandra -f
$ netstat -lptn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN - tcp6 0 0 127.0.0.3:9160 :::* LISTEN 8520/java tcp6 0 0 127.0.0.2:9160 :::* LISTEN 8424/java tcp6 0 0 127.0.0.1:9160 :::* LISTEN 8336/java tcp6 0 0 :::46954 :::* LISTEN 8424/java tcp6 0 0 :::53418 :::* LISTEN 8336/java tcp6 0 0 :::49035 :::* LISTEN 8520/java tcp6 0 0 :::80 :::* LISTEN - tcp6 0 0 :::42737 :::* LISTEN 8520/java tcp6 0 0 :::8081 :::* LISTEN 8336/java tcp6 0 0 :::8082 :::* LISTEN 8424/java tcp6 0 0 :::8083 :::* LISTEN 8520/java tcp6 0 0 :::60310 :::* LISTEN 8424/java tcp6 0 0 :::46167 :::* LISTEN 8336/java tcp6 0 0 ::1:631 :::* LISTEN - tcp6 0 0 127.0.0.3:7000 :::* LISTEN 8520/java tcp6 0 0 127.0.0.2:7000 :::* LISTEN 8424/java tcp6 0 0 127.0.0.1:7000 :::* LISTEN 8336/java
$ cd /opt/apache-cassandra-0.6.5/
$ ./bin/nodetool -h localhost -p 8081 ring
Address Status Load Range Ring
142865723918937898194528652808268231850
127.0.0.1 Up 3,1 KB 39461784941927371686416024510057184051 |<--|
127.0.0.3 Up 3,1 KB 54264004217607518447601711663387808864 | |
127.0.0.2 Up 2,68 KB 142865723918937898194528652808268231850 |-->|

$ ./bin/cassandra-cli --host node1 --port 9160 Connected to: "Test Cluster" on node1/9160 Welcome to cassandra CLI. cassandra> set Keyspace1.Standard1['rowkey']['column'] = 'value' Value inserted. cassandra> get Keyspace1.Standard1['rowkey']['column'] => (column=636f6c756d6e, value=value, timestamp=1285273581745000) cassandra> get Keyspace1.Standard1['rowkey'] => (column=636f6c756d6e, value=value, timestamp=1285273581745000) Returned 1 results. cassandra> del Keyspace1.Standard1['rowkey'] row removed.
@HelenaBean(keyspace="ContactList", columnFamily="Groups")
public class Group {
private Integer id;
private String name;
public Group() {
}
public Group(Integer id, String name) {
this.id = id;
this.name = name;
}
@KeyProperty
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Group [id=" + id + ", name=" + name + "]";
}
}
public class GroupTest {
static HelenaORMDAOFactory factory;
private HelenaDAO<Group> dao;
private static final Integer GROUP_ID = 123;
private static final String GROUP_NAME = "Test Group";
@BeforeClass
public static void setUpBeforeClass() throws Exception {
factory = HelenaORMDAOFactory.withConfig(
"localhost", 9160, SerializeUnknownClasses.YES);
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
factory = null;
}
@Before
public void setUp() throws Exception {
dao = factory.makeDaoForClass(Group.class);
}
@After
public void tearDown() throws Exception {
dao = null;
}
@Test
public void testSave() {
System.out.println("GroupDAOTest.testSave()");
Group group = new Group();
group.setId(GROUP_ID);
group.setName(GROUP_NAME);
System.out.println("Saving group: " + group);
dao.insert(group);
Assert.assertTrue(true);
Group retrieved = dao.get(GROUP_ID.toString());
System.out.println("Retrieved group: " + retrieved);
Assert.assertNotNull(retrieved);
Assert.assertEquals(GROUP_ID, retrieved.getId());
Assert.assertEquals(GROUP_NAME, retrieved.getName());
}
@Test
public void testRetrieve() {
System.out.println("GroupDAOTest.testRetrieve()");
System.out.println("Saving groups");
for (int i = 1; i <= 10; i++) {
Group group = new Group();
group.setId(GROUP_ID * 100 + i);
group.setName(GROUP_NAME + " " + i);
dao.insert(group);
}
List<Group> list = dao.getRange("", "", 10);
System.out.println("Retrieving groups");
Assert.assertNotNull(list);
Assert.assertFalse(list.isEmpty());
Assert.assertTrue(list.size() >= 10);
System.out.println("Retrieved list:");
for (Group group : list) {
System.out.println("- " + group);
}
}
@Test
public void testRemove() {
System.out.println("GroupDAOTest.testRemove()");
Group group = new Group();
group.setId(GROUP_ID);
group.setName(GROUP_NAME);
System.out.println("Saving group: " + group);
dao.insert(group);
System.out.println("Removing group: " + group);
dao.delete(group);
Assert.assertTrue(true);
Group retrieved = dao.get(GROUP_ID.toString());
System.out.println("Retrieved group: " + retrieved);
Assert.assertNull(retrieved);
}
}
cassandra> count ContactList.Groups['12305']
1 column
cassandra> get ContactList.Groups['12305']
=> (column=name, value=Test Group 5, timestamp=1283287882927)
Returned 1 result.
cassandra> get ContactList.Groups['12305']['name']
=> (column=name, value=Test Group 5, timestamp=1283287882927)
<Keyspace Name="ContactList">
<ColumnFamily CompareWith="UTF8Type" Name="Groups"/>
<ColumnFamily CompareWith="UTF8Type" Name="Contacts"/>
<ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
<ReplicationFactor>1</ReplicationFactor>
<EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
</Keyspace>
Connected to: "Test Cluster" on localhost/9160
Welcome to cassandra CLI.
Type 'help' or '?' for help. Type 'quit' or 'exit' to quit.
cassandra>
cassandra> show keyspaces
ContactList
Keyspace1
system
cassandra> describe keyspace ContactList
ContactList.Groups
Column Family Type: Standard
Columns Sorted By: org.apache.cassandra.db.marshal.UTF8Type@33b121
Column Family Type: Standard
Column Sorted By: org.apache.cassandra.db.marshal.UTF8Type
flush period: null minutes
------
ContactList.Contacts
Column Family Type: Standard
Columns Sorted By: org.apache.cassandra.db.marshal.UTF8Type@1b22920
Column Family Type: Standard
Column Sorted By: org.apache.cassandra.db.marshal.UTF8Type
flush period: null minutes
------
public class Group {
private Integer id;
private String name;
public Group() {
}
public Group(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Group [id=" + id + ", name=" + name + "]";
}
}
public interface IGroupDAO {
void startup();
void shutdown();
List<Group> findAll();
Group findById(Integer id);
void save(Group group);
void remove(Group group);
}
public class GroupDAOTest {
private static IGroupDAO dao;
private static final Integer GROUP_ID = 123;
private static final String GROUP_NAME = "Test Group";
@BeforeClass
public static void setUpBeforeClass() throws Exception {
dao = new GroupDAO();
dao.startup();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
dao.shutdown();
dao = null;
}
@Test
public void testSave() {
System.out.println("GroupDAOTest.testSave()");
Group group = new Group();
group.setId(GROUP_ID);
group.setName(GROUP_NAME);
System.out.println("Saving group: " + group);
dao.save(group);
Assert.assertTrue(true);
Group retrieved = dao.findById(GROUP_ID);
System.out.println("Retrieved group: " + retrieved);
Assert.assertNotNull(retrieved);
Assert.assertEquals(GROUP_ID, retrieved.getId());
Assert.assertEquals(GROUP_NAME, retrieved.getName());
}
@Test
public void testRetrieve() {
System.out.println("GroupDAOTest.testRetrieve()");
System.out.println("Saving groups");
for (int i = 1; i <= 10; i++) {
Group group = new Group();
group.setId(GROUP_ID * 100 + i);
group.setName(GROUP_NAME + " " + i);
dao.save(group);
}
List<Group> list = dao.findAll();
System.out.println("Retrieving groups");
Assert.assertNotNull(list);
Assert.assertFalse(list.isEmpty());
Assert.assertTrue(list.size() >= 10);
System.out.println("Retrieved list:");
for (Group group : list) {
System.out.println("- " + group);
}
}
@Test
public void testRemove() {
System.out.println("GroupDAOTest.testRemove()");
Group group = new Group();
group.setId(GROUP_ID);
group.setName(GROUP_NAME);
System.out.println("Saving group: " + group);
dao.save(group);
System.out.println("Removing group: " + group);
dao.remove(group);
Assert.assertTrue(true);
Group retrieved = dao.findById(GROUP_ID);
System.out.println("Retrieved group: " + retrieved);
Assert.assertNull(retrieved);
}
}
public class GroupDAO implements IGroupDAO {
private static final String KEYSPACE = "ContactList";
private static final String COLUMN_FAMILY = "Groups";
private static final String ENCODING = "utf-8";
private static TTransport tr = null;
/**
* Close the connection to the Cassandra Database.
*/
private static void closeConnection() {
try {
tr.flush();
tr.close();
} catch (TTransportException exception) {
exception.printStackTrace();
}
}
/**
* Open up a new connection to the Cassandra Database.
*
* @return the Cassandra Client
*/
private static Cassandra.Client setupConnection() {
try {
tr = new TSocket("localhost", 9160);
TProtocol proto = new TBinaryProtocol(tr);
Cassandra.Client client = new Cassandra.Client(proto);
tr.open();
return client;
} catch (TTransportException exception) {
exception.printStackTrace();
}
return null;
}
private Cassandra.Client client;
@Override
public void startup() {
this.client = setupConnection();
}
@Override
public void shutdown() {
closeConnection();
}
@Override
public List<Group> findAll() {
List<Group> list = new ArrayList<Group>();
try {
KeyRange keyRange = new KeyRange();
keyRange.setStart_key("");
keyRange.setEnd_key("");
SliceRange sliceRange = new SliceRange();
sliceRange.setStart(new byte[] {});
sliceRange.setFinish(new byte[] {});
SlicePredicate slicePredicate = new SlicePredicate();
slicePredicate.setSlice_range(sliceRange);
ColumnParent columnParent = new ColumnParent(COLUMN_FAMILY);
List<KeySlice> keySlices = client.get_range_slices(KEYSPACE,
columnParent, slicePredicate, keyRange,
ConsistencyLevel.ONE);
if (keySlices == null || keySlices.isEmpty())
return list;
for (KeySlice keySlice : keySlices) {
Group group = new Group();
group.setId(Integer.parseInt(keySlice.getKey()));
for (ColumnOrSuperColumn c : keySlice.getColumns()) {
if (c.getColumn() != null) {
String name = new String(c.getColumn().getName(),
ENCODING);
String value = new String(c.getColumn().getValue(),
ENCODING);
// long timestamp = c.getColumn().getTimestamp();
if (name.equals("name")) {
group.setName(value);
}
}
}
list.add(group);
}
return list;
} catch (Exception exception) {
exception.printStackTrace();
}
return null;
}
@Override
public Group findById(Integer id) {
try {
SlicePredicate slicePredicate = new SlicePredicate();
SliceRange sliceRange = new SliceRange();
sliceRange.setStart(new byte[] {});
sliceRange.setFinish(new byte[] {});
slicePredicate.setSlice_range(sliceRange);
ColumnParent columnParent = new ColumnParent(COLUMN_FAMILY);
List<ColumnOrSuperColumn> result = client.get_slice(KEYSPACE, id
.toString(), columnParent, slicePredicate,
ConsistencyLevel.ONE);
if (result == null || result.isEmpty())
return null;
Group group = new Group();
group.setId(id);
for (ColumnOrSuperColumn c : result) {
if (c.getColumn() != null) {
String name = new String(c.getColumn().getName(), ENCODING);
String value = new String(c.getColumn().getValue(), ENCODING);
// long timestamp = c.getColumn().getTimestamp();
if (name.equals("name")) {
group.setName(value);
}
}
}
return group;
} catch (Exception exception) {
exception.printStackTrace();
}
return null;
}
@Override
public void save(Group group) {
try {
long timestamp = System.currentTimeMillis();
Map<String, List<ColumnOrSuperColumn>> job = new HashMap<String, List<ColumnOrSuperColumn>>();
List<ColumnOrSuperColumn> columns = new ArrayList<ColumnOrSuperColumn>();
Column column = new Column("name".getBytes(ENCODING), group
.getName().getBytes(ENCODING), timestamp);
ColumnOrSuperColumn columnOrSuperColumn = new ColumnOrSuperColumn();
columnOrSuperColumn.setColumn(column);
columns.add(columnOrSuperColumn);
job.put(COLUMN_FAMILY, columns);
client.batch_insert(KEYSPACE, group.getId().toString(), job,
ConsistencyLevel.ALL);
} catch (Exception exception) {
exception.printStackTrace();
}
}
@Override
public void remove(Group group) {
try {
ColumnPath columnPath = new ColumnPath(COLUMN_FAMILY);
client.remove(KEYSPACE, group.getId().toString(), columnPath,
System.currentTimeMillis(), ConsistencyLevel.ALL);
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
cassandra> count ContactList.Groups['12301']
1 columns
cassandra> get ContactList.Groups['12301']
=> (column=name, value=Test Group 1, timestamp=1283287745613)
Returned 1 results.
cassandra> get ContactList.Groups['12301']['name']
=> (column=name, value=Test Group 1, timestamp=1283287745613)