Desenvolvimento em Java 2.0: armazenamento em nuvem com o SimpleDB da Amazon – Parte 01
O Bigtable, do Google, 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, sejam os corredores de uma corrida.
A falta de esquema do Bigtable oferece uma enorme flexibilidade, que suporta o desenvolvimento
rápido.
Mas 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, um armazenamento de
dados de chave/valor extremamente escalável e confiável, que é exposto via interface 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.
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.
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
o 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
Em outros artigos desta série, usei 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 dos 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 a 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<br /> File("etc/AwsCredentials.properties"))); String domain = "Races"; sdb.createDomain(new<br /> 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<br />ArrayList<ReplaceableItem>(); data.add(new<br />ReplaceableItem().withName("Race_01").withAttributes( new<br />ReplaceableAttribute().withName("Name").withValue("<br />Charlottesville Marathon"), new<br />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 =<br />"select * from `" + domain + "` where Name = 'Charlottesville Marathon'"; SelectRequest<br />selectRequest = new SelectRequest(qry); <br />for (Item item : sdb.select(selectRequest).getItems()) {<br />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<br />ArrayList<ReplaceableItem>(); data2.add(new<br />ReplaceableItem().withName("Race_02").withAttributes( new<br />ReplaceableAttribute().withName("Name").withValue("Charlottesville 1/2 Marathon"), new<br />ReplaceableAttribute().withName("Distance").withValue("13.1")));<br />sdb.batchPutAttributes(new<br />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<br />disQry = "select * from `" + domain + "` where Distance > '13.1'"; SelectRequest<br />selectRequest = new SelectRequest(disQry); for (Item item :<br />sdb.select(selectRequest).getItems()) {<br />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<br />ArrayList<ReplaceableItem>(); data3.add(new<br />ReplaceableItem().withName("Race_03").withAttributes( new<br />ReplaceableAttribute().withName("Name").withValue("Leesburg Ultra Marathon"), new<br />ReplaceableAttribute().withName("Distance").withValue("103.1"))); <br />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<br />disQry = "select * from `" + domain + "` where Distance < '13.1'"; SelectRequest<br />selectRequest = new SelectRequest(disQry); for (Item item :<br />sdb.select(selectRequest).getItems()) { <br />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, ao final):
Figura 1. Preenchimento de valores de
distância

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

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"));<br />List<ReplaceableItem> runners = new ArrayList<ReplaceableItem>();<br />runners.add(new ReplaceableItem().withName("Runner_01").withAttributes( new<br />ReplaceableAttribute().withName("Name").withValue("Sally Smith"))); <br />runners.add(new ReplaceableItem().withName("Runner_02").withAttributes( <br />new ReplaceableAttribute().withName("Name").withValue("Richard Bean"))); <br />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<br /> ReplaceableAttribute().withName("Name").withValue("Charlottesville Marathon"), new<br /> ReplaceableAttribute().withName("Distance").withValue("026.2"), new<br /> ReplaceableAttribute().withName("Runners").withValue("Runner_01"), new<br /> 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
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. - Procure na livraria tecnológica
livros sobre esses e outros assuntos técnicos. -
Zona de tecnologia Java do developerWorks:
Encontre centenas de artigos sobre cada aspecto da programação Java.
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
- Participe da comunidade My developerWorks.
Artigo publicado originalmente em My developerWorks, por Andrew Glover
Andrew
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.







