Desenvolvimento em Java 2.0: Armazenamento em nuvem com o SimpleDB da Amazon, Parte 1

Introdução ao SimpleDB e Amazon SDK

Parte da família de serviços da Web da Amazon, o SimpleDB é um armazenamento de dados de chave/valor extremamente escalável e confiável, que é exposto via interface da Web e que pode ser acessado usando a linguagem Java. Neste primeiro de dois artigos, teremos uma introdução ao SimpleDB da Amazon, explorando sua abordagem exclusiva ao armazenamento de dados sem esquema, incluindo uma demonstração de um dos recursos mais incomuns do armazenamento de dados: busca lexicográfica.

Andrew Glover, Author and developer

Andrew GloverAndrew Glover é desenvolvedor, autor, palestrante e empresário com uma paixão por desenvolvimento orientado a comportamento, Integração Contínua e desenvolvimento de software Agile. É possível entrar em contato com ele em seu blog.



29/Set/2010

Sobre esta série

O panorama do desenvolvimento em Java passou por mudanças radicais desde que a tecnologia Java surgiu. Graças a estruturas maduras de software livre e a infraestruturas de implementação confiáveis para aluguel, agora é possível montar, testar, executar e manter aplicativos Java de maneira rápida e barata. Nesta série, Andrew Glover explora o leque de tecnologias e ferramentas que torna possível esse novo paradigma de desenvolvimento em Java.

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).

'Consistência eventual' do SimpleDB

O teorema CAP (veja Recursos) declara que um sistema distribuído não pode ser altamente disponível, escalável e garantir a consistência, tudo isso ao mesmo tempo; em vez disso um sistema distribuído só pode suportar duas dessas três qualidades em qualquer momento. Assim, o SimpleDB garante armazenamento de dados altamente disponível e escalável que não suporta consistência imediata. O que o SimpleDB suporta é a consistência eventual, que não é tão ruim quanto parece.

No caso da Amazon, consistência eventual significa que as coisas ficam consistentes entre todos os nós (embora dentro de uma região) em segundos. O que trocamos por aquela fração de tempo onde dois processos simultâneos podem (apenas podem) estar lendo duas instâncias diferentes dos mesmos dados é enorme confiabilidade a um preço financeiramente muito suportável. (Basta fazer um orçamento com entidades comerciais que oferecem confiabilidade similar para ver a diferença.)

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

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

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

Aprender

Obter produtos e tecnologias

  • 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.

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que acessar o developerWorks, um perfil será criado para você. Informações do seu perfil (tais como: nome, país / região, e empresa) estarão disponíveis ao público, que poderá acompanhar qualquer conteúdo que você publicar. Seu perfil no developerWorks pode ser atualizado a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


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