Conteúdo


Desenvolvimento em Java 2.0

Armazenamento em nuvem com o SimpleDB da Amazon, Parte 1

Introdução ao SimpleDB e Amazon SDK

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: Desenvolvimento em Java 2.0

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:Desenvolvimento em Java 2.0

Fique ligado em conteúdos adicionais dessa série.

Ao longo desta série, compartilhei com vocês diversos armazenamentos de dados não relacionais, coletivamente chamados de NoSQL. Em um artigo recente, eu demonstrei como um armazenamento de dados orientado a documentos (CouchDB) difere totalmente de bancos de dados relacionais orientados a esquemas. Além disso, a inteira API do CouchDB é RESTful e suporta um método diferente de consulta: Funções MapReduce definidas em JavaScript. É evidente que isso rompe com o mundo tradicional de JDBC.

Recentemente escrevi também sobre o Bigtable da Google, que não é uma solução de dados relacionais nem orientada a documentos (e, na verdade, não suporta JDBC de nenhuma maneira). O Bigtable é o que é conhecido como armazenamento de chave/valor. Ou seja, ele não tem esquema e permite armazenar basicamente qualquer coisa que quisermos, seja uma instância de multa de estacionamento, uma lista de corridas, seja os corredores de uma corrida. A falta de esquema do Bigtable oferece uma enorme flexibilidade, que suporta o desenvolvimento rápido.

O Bigtable não é o único armazenamento de dados de chave/valor que podemos escolher. A Amazon tem seu próprio armazenamento de chave/valor baseado em nuvem chamado de SimpleDB. Enquanto o Bigtable é exposto aos desenvolvedores Java por meio de uma abstração facilitada pelo Google App Engine, o SimpleDB da Amazon é exposto por meio de uma interface de serviço da Web. Assim, é possível manipular o armazenamento de dados SimpleDB por meio da Web e de HTTP. Ligações em cima da infraestrutura de serviço da Web da Amazon possibilitam aproveitar o SimpleDB usando a linguagem que quisermos, entre opções que incluem PHP, Ruby, C# e linguagem Java.

Este mês, vou apresentar o SimpleDB por meio do SDK oficial da Amazon. Vou usar outro exemplo de corrida para demonstrar uma das facetas mais incomuns desse poderoso armazenamento de dados baseado em nuvem: busca lexicográfica.

Apresentação do SimpleDB

Internamente, o SimpleDB é um armazenamento de dados extremamente escalável e altamente disponível escrito em Erlang. Conceitualmente, é semelhante ao S3 da Amazon. Enquanto o S3 tem objetos localizados em depósitos, o SimpleDB é logicamente definido como domínios que contêm itens. O SimpleDB também permite que os itens contenham atributos. Pense em um domínio mais ou menos como se fosse um depósito no S3, ou uma tabela no sentido relacional (ou mais apropriadamente, a noção de "kind" (tipo) do Bigtable). Mas cuidado para não projetar a qualidade relacional no seu conceito de SimpleDB, porque na verdade ele é tão sem esquema como o Bigtable. Os domínios podem ter muitos itens (similares a linhas) e os itens podem ter muitos atributos (que são como as colunas do mundo relacional).

Atributos são, na verdade, apenas pares de nome/valor (parece o Bigtable, não é?) e o aspecto do "par" não se limita a um valor. Ou seja, um nome de atributo pode ter uma coleção (ou lista) de valores associados; um item de palavra pode, por exemplo, ter valores de atributo com definição múltipla. Além disso, todos os dados dentro do SimpleDB são representados como String, o que é totalmente diferente do Bigtable ou até do RDBMS padrão, que geralmente suporta milhares de tipos de dados.

A abordagem de tipo de dados único do SimpleDB para os valores de atributo pode ser um benefício ou uma limitação, dependendo do seu ponto de vista. Seja como for, tem implicações em como as consultas são executadas (logo falaremos mais sobre isso). O SimpleDB também não suporta a noção de junções entre domínios, de modo que não é possível consultar itens em múltiplos domínios. Contudo, é possível superar essa limitação executando múltiplas consultas no SimpleDB e fazendo a junção no fim.

Os itens não têm chaves propriamente ditas (como tem o Bigtable). A chave ou identificador exclusivo de um item é seu nome. O SimpleDB também é suficientemente inteligente para atualizar o item quando é emitida uma solicitação de criação de duplicata, desde que os atributos desse item tenham sido alterados.

Com outros serviços da Web da Amazon, o SimpleDB expõe tudo por meio de HTTP, de modo que há inúmeros modos de fazer uma interface com ele. No mundo Java, nossas opções vão do próprio SDK da Amazon (que usaremos nos exemplos a seguir) passando por um projeto popular chamado Topica até implementações JPA completas (que exploraremos na Parte 2).

Corridas nas nuvens

Até este ponto, usei nesta séria uma analogia de uma corrida e de uma multa de estacionamento para demonstrar os recursos das várias tecnologias Java 2.0. Usar um domínio de problema familiar facilita o entendimento das diferenças e pontos em comum entre os sistemas. Então, vamos continuar com a analogia da linha de chegada aqui para ver como corredores e corridas são representados no SimpleDB da Amazon.

No SimpleDB, podemos modelar uma corrida como um domínio. A instância da corrida seria um item no SimpleDB e seu nome e data seriam expressos como atributos (com valores). É importante notar que o nome nesse caso é um atributo e não o nome do próprio item. O nome dado a uma instância de item se torna sua chave. A chave para esse item pode ser o nome da maratona. Como alternativa, em vez de limitar a instância da corrida a um ponto no tempo (corridas costumam ser eventos anuais), podemos dar ao item um nome exclusivo (como um registro de data e hora), que nos permitiria armazenar múltiplas corridas bianuais no SimpleDB.

De modo similar, runner seria um domínio. Os corredores individuais seriam itens e o nome e idade do corredor seriam atributos. Assim como acontece em uma corrida, cada instância de item runner precisaria de um nome exclusivo (que diferenciasse Pedro da Silva de Marcos Oliveira, por exemplo). Diferentemente do Bigtable, o SimpleDB não se importa com o nome dado a cada item e, de fato, não fornece um gerador de chaves. Talvez nesse caso pudéssemos usar um registro de data e hora ou apenas incrementar o contador para cada corredor, como runner_1, runner_2, etc.

Visto que não há esquema, os itens individuais ficam livres para variar seus atributos. De modo similar, é possível variar os itens de domínio se desejado. Mas é bom limitar essa variabilidade visto que ela tende a deixar os dados desorganizados e, assim, difíceis de localizar ou gerenciar. Preste atenção às minhas palavras: Queira ou não queira, dados sem esquema e desorganizados são a receita para o desastre!

Correndo com o SDK da Amazon

A Amazon recentemente padronizou uma biblioteca que contém código para trabalhar com todos os seus serviços da Web, incluindo o SimpleDB. Essa biblioteca, como a maioria, resume a comunicação subjacente necessária para acessar e usar esses serviços, permitindo que os clientes trabalhem de modo nativo. Por exemplo, a biblioteca Java da Amazon para o SimpleDB permite criar domínios e itens, consultá-los e, é claro, atualizá-los e removê-los do armazenamento — o tempo todo sem se dar conta dessas operações que percorrem o HTTP até a nuvem.

A listagem 1 mostra um AmazonSimpleDBClient definido usando código Java simples juntamente com um domínio Races. (Será preciso criar uma conta na Amazon se quiser duplicar esse exercício na sua estação de trabalho.)

Listagem 1. Criando uma instância do cliente de SimpleDB da Amazon
AmazonSimpleDB sdb = new AmazonSimpleDBClient(new PropertiesCredentials(
                new File("etc/AwsCredentials.properties")));
String domain = "Races";
sdb.createDomain(new CreateDomainRequest(domain));

Note que o padrão do SDK da Amazon de objetos Request permanecerá em todas as atividades do SimpleDB. Nesse caso, criar um CreateDomainRequest cria um domínio. É possível adicionar itens por meio do método batchPutAttributes de cliente, que basicamente pega um List de itens como os mostrados na listagem 2:

Listagem 2. Corrida_01
List<ReplaceableItem> data = new ArrayList<ReplaceableItem>();

data.add(new ReplaceableItem().withName("Race_01").withAttributes(
   new ReplaceableAttribute().withName("Name").withValue("Charlottesville Marathon"),
   new ReplaceableAttribute().withName("Distance").withValue("26.2")));

No SDK da Amazon, Item são representados como ReplaceableItem tipos. Damos um nome para cada instância (ou seja, uma chave) e depois podemos adicionar atributos (de tipo ReplaceableAttribute). Na listagem 2, criei uma corrida, uma maratona, com a chave simples "Race_01". Adiciono essa instância ao meu domínio Races criando um BatchPutAttributesRequset e enviando-o juntamente com o AmazonSimpleDBClient, como mostrado na listagem 3:

Listagem 3. Criando um item em SimpleDB
sdb.batchPutAttributes(new BatchPutAttributesRequest(domain, data));

Consultas em SimpleDB

Agora que tenho uma corrida salva, posso, naturalmente, consultá-la por meio da linguagem de consulta do SimpleDB, que é muito semelhante a SQL. Mas há uma pegadinha. Lembra que eu disse que todos os valores de atributo de item são armazenados como String? Isso quer dizer que as comparações de dados são feitas lexicograficamente, o que repercute nas buscas.

Por exemplo, se executarmos uma consulta com base em números, o SimpleDB buscará com base em caracteres e não em valores de número inteiro. No momento, tenho uma única instância de corrida armazenada no SimpleDB, e posso procurá-la facilmente usando as instruções do SimpleDB, que são semelhantes a SQL, como mostrado na listagem 4:

Listagem 4. Buscando Corrida_01
String qry = "select * from `" + domain + "` where Name = 'Charlottesville Marathon'";
SelectRequest selectRequest = new SelectRequest(qry);
for (Item item : sdb.select(selectRequest).getItems()) {
 System.out.println("Race Name: " + item.getName());
}

A consulta na listagem 4 parece SQL normal. Nesse caso, vou simplesmente solicitar todas as instâncias de Race onde Name é igual a "Maratona de Charlottesville". Enviar um SelectRequest para AmazonSimpleDBClient fornece uma coleção de Item como resultado. Assim, posso fazer a iteração sobre todos os itens e imprimir seus nomes e, nesse caso, só me será fornecido um único item.

Mas agora, vejamos o que acontece quando adicionamos outra corrida com um atributo de distância diferente, mostrado na listagem 5:

Listagem 5. Corrida mais curta
List<ReplaceableItem> data2 = new ArrayList<ReplaceableItem>();

data2.add(new ReplaceableItem().withName("Race_02").withAttributes(
   new ReplaceableAttribute().withName("Name").withValue("Charlottesville 1/2 Marathon"),
   new ReplaceableAttribute().withName("Distance").withValue("13.1")));

sdb.batchPutAttributes(new BatchPutAttributesRequest(domain, data2));

Com duas corridas de distâncias diferentes, faz sentido buscar com base na distância, como mostrado na listagem 6:

Listagem 6. Buscando por distância
String disQry = "select * from `" + domain + "` where Distance > '13.1'";
SelectRequest selectRequest = new SelectRequest(disQry);
for (Item item : sdb.select(selectRequest).getItems()) {
 System.out.println("Race Name: " + item.getName());
}

Com certeza, a corrida apresentada é Race_01, que é a correta: 26,2 e maior que 13,1, tanto matemática como lexicograficamente. Mas vejamos o que acontece quando acrescento uma corrida realmente grande a essa mistura:

Listagem 7. Ultramaratona de Leesburg
List<ReplaceableItem> data3 = new ArrayList<ReplaceableItem>();

data3.add(new ReplaceableItem().withName("Race_03").withAttributes(
   new ReplaceableAttribute().withName("Name").withValue("Leesburg Ultra Marathon"),
   new ReplaceableAttribute().withName("Distance").withValue("103.1")));

sdb.batchPutAttributes(new BatchPutAttributesRequest(domain, data3));

Na listagem 7, acrescentei uma corrida com distância de 103,1. Quando executo novamente a consulta da listagem 6, o que será que acontece? É verdade: 103,1, lexicograficamente falando, é menor que 13,1, não maior. É por isso que (se estiver me acompanhando) não vemos a Ultramaratona de Leesburg alistada!

Agora, vejamos o que acontece se eu executar outra consulta que procura corridas mais curtas, como mostrado na listagem 8:

Listagem 8. Vejamos o que aparece!
String disQry = "select * from `" + domain + "` where Distance < '13.1'";
SelectRequest selectRequest = new SelectRequest(disQry);
for (Item item : sdb.select(selectRequest).getItems()) {
 System.out.println("Race Name: " + item.getName());
}

Um olhar desatento se surpreenderá com o resultado da execução da consulta na listagem 8. Mas sabendo que as buscas são executadas lexicograficamente, ele faz muito sentido — embora estejamos procurando uma corrida curta, a (fictícia) Ultramaratona de Leesburg não é o que queremos!

Busca lexicográfica

Essa busca pode causar problemas quando procuramos dados numerados (incluindo datas), mas nem tudo está perdido. Um modo de corrigir o problema de busca por distância é preenchendo os números usados para os atributos de distância.

Atualmente, minha corrida mais longa tem 103,6 milhas (embora pessoalmente eu nunca tenha chegado nem perto de correr essa distância!), que é mostrada lexicograficamente como três dígitos à esquerda da vírgula decimal. Assim, basta preencher as outras corridas com zeros à esquerda, dando a todas elas o mesmo número de caracteres. Fazer isso fará com que minhas buscas baseadas em distância funcionem.

A Figura 1 é uma captura de tela da SDB Tool, um plug-in do Firefox para consultar visualmente e atualizar os domínios do banco de dados do Simple DB (veja Recursos):

Figura 1. Preenchimento de valores de distância
A screenshot showing races and distance values as displayed by SDB Tool
A screenshot showing races and distance values as displayed by SDB Tool

Como dá para ver, acrescentei um zero aos valores de distância de Race_01 e Race_02. Embora isso talvez não faça muito sentido para quem não entende do assunto, tornará a busca muito mais fácil. Assim, na Figura 2, pode-se ver que fiz uma pesquisa de corridas com distância de menos de 020,0 milhas (ou, para simplificar 20 milhas), e veja que finalmente — e — aparecem corretamente:

Figura 2. A busca com preenchimento resolve o problema
A screeenshot showing successful query results in SDB Tool
A screeenshot showing successful query results in SDB Tool

Com um pouco de planejamento, não é difícil superar o que pode ser encarado como fator limitador das buscas lexicográficas. Se preenchimento não for a sua praia, outra opção é filtrar do lado do aplicativo das coisas. Ou seja, é possível manter seus números inteiros como números inteiros normais e filtrar as coisas depois de obter uma coleção de itens não filtrados na Amazon — ou seja, emitir um select * em todos os itens. Mas essa abordagem pode ser cara se você tiver muitos dados.

Relacionamentos no SimpleDB

Não é difícil configurar relacionamentos no SimpleDB. Conceitualmente, é fácil criar um atributo de um item de corredor chamado race e colocar nele o nome da corrida (como Race_01). Melhor ainda, não há nada que o impeça de manter uma coleção de nomes de corridas nesse valor. O inverso também é verdadeiro: É fácil manter uma coleção de nomes de corredores em um domínio race (mostrado na listagem 9). Basta lembrar: Na verdade não é possível juntar os dois domínios por meio da linguagem de consulta da Amazon; será preciso fazer isso no fim.

Listagem 9. Criando o domínio Corredores e dois corredores
sdb.createDomain(new CreateDomainRequest("Runners"));

List<ReplaceableItem> runners = new ArrayList<ReplaceableItem>();

runners.add(new ReplaceableItem().withName("Runner_01").withAttributes(
   new ReplaceableAttribute().withName("Name").withValue("Sally Smith")));

runners.add(new ReplaceableItem().withName("Runner_02").withAttributes(
	new ReplaceableAttribute().withName("Name").withValue("Richard Bean")));

sdb.batchPutAttributes(new BatchPutAttributesRequest("Runners", runners));

Depois de criar um domínio Runners e adicionar corredores a ele, é possível atualizar uma corrida existente e adicionar corredores a ela, como na listagem 10:

Listagem 10. Atualizando uma corrida para conter dois corredores
races.add(new ReplaceableItem().withName("Race_01").withAttributes(
  new ReplaceableAttribute().withName("Name").withValue("Charlottesville Marathon"),
  new ReplaceableAttribute().withName("Distance").withValue("026.2"),
  new ReplaceableAttribute().withName("Runners").withValue("Runner_01"),
  new ReplaceableAttribute().withName("Runners").withValue("Runner_02")));

Em resumo, relacionamentos são possíveis, mas é preciso gerenciá-los fora do SimpleDB. Se quisermos obter o nome completo de todos os corredores em Race_01, por exemplo, será preciso obtê-los em uma consulta e depois enviar consultas (nesse caso duas, porque Race_01 tem apenas dois valores de atributo) com relação ao domínio runner para obter as respostas.

Operações de limpeza

Fazer uma limpeza depois do trabalho é importante, então vou concluir com uma limpeza rápida usando o SDK da Amazon. As operações de limpeza não são muito diferentes da criação de dados e consulta a eles; basta criar tipos Request e fazer exclusões.

Excluir Race_01 é muito fácil, como mostrado na listagem 11:

Listagem 11. Excluindo no SimpleDB
sdb.deleteAttributes(new DeleteAttributesRequest(domain, "Race_01"));

Se usei DeleteAttributesRequest para excluir um item, o que você acha que devo usar para excluir um domínio? Acertou: DeleteDomainRequest!

Listagem 12. Excluindo um domínio no SimpleDB
sdb.deleteDomain(new DeleteDomainRequest(domain));

Este tour ainda não acabou!

Ainda não terminamos nosso passeio pelas nuvens do SimpleDB da Amazon, mas por enquanto terminamos com o Amazon SDK. O SDK da Amazon é funcional e pode ser útil até certo ponto, mas se quiser modelar coisas — corridas e corredores — pode ser preciso aproveitar algo como JPA. No mês que vem, descobriremos o que acontece quando combinamos JPA com SimpleDB. Até lá, divirta-se com suas buscas lexicográficas!


Recursos para download


Temas relacionados

  • Java development 2.0: Esta série do developerWorks explora tecnologias e ferramentas que estão redefinindo o cenário de desenvolvimento Java, incluindo CouchDB (novembro de 2009) e Bigtable (maio de 2010).
  • "Cloud computing with Amazon Web Services, Part 5: Dataset processing in the cloud with SimpleDB" (Prabhakar Chaganti, developerWorks, fevereiro de 2009): Aprenda conceitos básicos do Amazon SimpleDB (SDB) e explore algumas das funções fornecidas pela boto, uma biblioteca Python de software livre para interagir com o SDB.
  • "Eventually Consistent - Revisited" (Werner Vogels, All Things Distributed, dezembro de 2008): O CTO da Amazon explica o Teorema CAP de Eric Brewer e seu impacto na infraestrutura de serviços da Web da Amazon.
  • "NoSQL Patterns" (Ricky Ho, Pragmatic Programming Techniques, novembro de 2009): Uma visão geral e listagens de bancos de dados NoSQL, seguida por um olhar aprofundado sobre a arquitetura comum de bancos de dados NoSQL.
  • "Bigtable: A Distributed Storage System for Structured Data" (Fay Chang et al., Google, novembro de 2006): Bigtable é um sistema de armazenamento distribuído para gerenciar dados estruturados que foi criado para ser escalado para um tamanho muito grande: petabytes de dados em milhares de servidores de bens.
  • Amazon SDK: Faça o download do SDK da Amazon e comece a aproveitar a infraestrutura de serviços da Web dela. (Será preciso criar uma conta na Amazon se já não tiver uma.)
  • SDB Tool: Plug-in da GUI do Firefox que facilita o uso do Amazon SimpleDB.

Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=499111
ArticleTitle=Desenvolvimento em Java 2.0: Armazenamento em nuvem com o SimpleDB da Amazon, Parte 1
publish-date=09292010