Consultando DynamoDB por data

102

Venho de um histórico de banco de dados relacional e estou tentando trabalhar com o DynamoDB do amazon

Eu tenho uma tabela com uma chave hash "DataID" e um intervalo "CreatedAt" e um monte de itens nela.

Estou tentando obter todos os itens que foram criados após uma data específica e classificados por data. O que é bastante simples em um banco de dados relacional.

No DynamoDB, a coisa mais próxima que consegui encontrar é uma consulta e usar a chave de intervalo maior que filtro. O único problema é que, para realizar uma consulta, preciso de uma chave hash que anula o propósito.

Então, o que estou fazendo de errado? Meu esquema de tabela está errado, a chave hash não deveria ser única? ou existe outra maneira de consultar?

applechief
fonte

Respostas:

34

Resposta atualizada:

O DynamoDB permite a especificação de índices secundários para auxiliar nesse tipo de consulta. Os índices secundários podem ser globais, o que significa que o índice abrange toda a tabela em chaves hash, ou locais, o que significa que o índice existiria dentro de cada partição de chave hash, exigindo que a chave hash também seja especificada ao fazer a consulta.

Para o caso de uso desta questão, você gostaria de usar um índice secundário global no campo "CreatedAt".

Para mais informações sobre os índices secundários do DynamoDB, consulte a documentação do índice secundário

Resposta Original:

O DynamoDB não permite pesquisas indexadas apenas na chave de intervalo. A chave hash é necessária para que o serviço saiba em qual partição procurar os dados.

É claro que você pode realizar uma operação de varredura para filtrar pelo valor de data, no entanto, isso exigiria uma varredura completa da tabela, por isso não é o ideal.

Se você precisar realizar uma pesquisa indexada de registros por tempo em várias chaves primárias, o DynamoDB pode não ser o serviço ideal para você usar ou pode ser necessário utilizar uma tabela separada (no DynamoDB ou em um armazenamento relacional) para armazenar itens metadados contra os quais você pode realizar uma pesquisa indexada.

Mike Brant
fonte
14
Veja os comentários na resposta abaixo; existem não maneiras de lidar com isso agora, pelo menos não para o que o OP perguntou. Os GSIs ainda exigem que você especifique uma chave hash, portanto, você não pode consultar todos os registros com CreatedAtmais de um determinado ponto.
pkaeding
4
@pkaeding está certo. Você pode obter registros mais antigos do que alguma data específica usando a varredura , mas não pode colocá-los em ordem de classificação. GSI não o ajudará neste caso. Não é possível classificar a chave de partição , nem é possível consultar apenas a chave de intervalo .
gkiko
15
Para aqueles de vocês confusos. ESTA RESPOSTA ESTÁ ERRADA. Sua resposta original está certa, mas sua resposta atualizada não. Leia a resposta de Warren Parad abaixo. Está certo.
Ryan Shillington
1
@MikeBrant Desejo consultar (não fazer a varredura, que examina todos os itens da tabela, tornando-a muito ineficiente e cara) uma tabela na chave hash GSI de uma tabela (CreatedAt) usando o símbolo maior que. Pelo que eu sei, isso não pode ser feito.
Aziz Javed
4
O problema que você provavelmente terá ao usar uma data como partição primária é que você pode criar um ponto de acesso em algum ou um dos pares, devido ao fato de que na maioria dos armazenamentos de dados, os novos dados são consultados com mais frequência do que os antigos.
Conhecimento de
53

Devido à sua estrutura de tabela atual, isso não é possível no DynamoDB. O grande desafio é entender que a chave Hash da tabela (partição) deve ser tratada como criação de tabelas separadas. Em alguns aspectos, isso é realmente poderoso (pense nas chaves de partição como a criação de uma nova tabela para cada usuário ou cliente, etc ...).

As consultas só podem ser feitas em uma única partição. Esse é realmente o fim da história. Isso significa que se você deseja consultar por data (você deseja usar mseg desde a época), todos os itens que deseja recuperar em uma única consulta devem ter o mesmo Hash (chave de partição).

Eu deveria qualificar isso. Você pode absolutamente scanpelo critério que está procurando, isso não é problema, mas isso significa que você estará olhando para cada linha em sua tabela e, em seguida, verificando se essa linha tem uma data que corresponda aos seus parâmetros. Isso é muito caro, especialmente se você estiver no negócio de armazenar eventos por data em primeiro lugar (ou seja, você tem muitas linhas).

Você pode ficar tentado a colocar todos os dados em uma única partição para resolver o problema, e você pode com certeza, no entanto, sua taxa de transferência será dolorosamente baixa, visto que cada partição recebe apenas uma fração do valor total definido.

A melhor coisa a fazer é determinar partições mais úteis para criar para salvar os dados:

  • Você realmente precisa olhar todas as linhas ou são apenas as linhas de um usuário específico?

  • Seria normal primeiro restringir a lista por mês e fazer várias consultas (uma para cada mês)? Ou por ano?

  • Se você estiver fazendo uma análise de série temporal, existem algumas opções, altere a chave de partição para algo calculado PUTpara tornar querymais fácil ou use outro produto aws como kinesis, que se presta apenas para adicionar log.

Warren Parad
fonte
4
Quero enfatizar a opção que você apresentou em seu último parágrafo sobre considerar "por ano". Crie um atributo como yyyye hash sobre isso, mas também crie uma createddata que você pode usar como sua chave de intervalo. Então você obtém 10 GB de dados por ano (27 MB por dia), o que provavelmente é adequado para mais circunstâncias. Significa que você precisa criar uma consulta por ano quando as consultas de data ultrapassam o limite do ano, mas pelo menos funcionará e é mais seguro do que criar uma chave hash fictícia.
Ryan Shillington
1
Outra opção: stackoverflow.com/questions/35963243/…
Ryan Shillington
1
como o link acima explica, as chaves de partição estritamente baseadas no tempo podem levar a pontos de acesso. se você deve usar chaves de partição baseadas em tempo, é melhor adicionar algum outro elemento à chave de partição para distribuir um período de tempo em várias partições. Tenho visto sugestões de apenas usar um prefixo entre 0-n, onde n é o número de partições em que cada intervalo de tempo deve ser distribuído.
dres
@RyanShillington Não há limite de 10 GB nos índices secundários globais . Esse limite se aplica apenas a índices secundários locais .
Simon Forsberg
18

A abordagem que eu segui para resolver esse problema é criar um Índice Secundário Global conforme abaixo. Não tenho certeza se essa é a melhor abordagem, mas espero que seja útil para alguém.

Hash Key                 | Range Key
------------------------------------
Date value of CreatedAt  | CreatedAt

Limitação imposta ao usuário da API HTTP para especificar o número de dias para recuperar dados, padronizado para 24 horas.

Dessa forma, sempre posso especificar o HashKey como o dia da data atual e o RangeKey pode usar os operadores> e <durante a recuperação. Dessa forma, os dados também são espalhados por vários fragmentos.

Gireesh
fonte
8

Sua chave Hash (primária do tipo) deve ser única (a menos que você tenha um intervalo como o declarado por outros).

No seu caso, para consultar sua tabela, você deve ter um índice secundário.

|  ID  | DataID | Created | Data |
|------+--------+---------+------|
| hash | xxxxx  | 1234567 | blah |

Sua chave Hash é o ID. Seu índice secundário é definido como: DataID-Created-index (esse é o nome que o DynamoDB usará)

Então, você pode fazer uma consulta como esta:

var params = {
    TableName: "Table",
    IndexName: "DataID-Created-index",
    KeyConditionExpression: "DataID = :v_ID AND Created > :v_created",
    ExpressionAttributeValues: {":v_ID": {S: "some_id"},
                                ":v_created": {N: "timestamp"}
    },
    ProjectionExpression: "ID, DataID, Created, Data"
};

ddb.query(params, function(err, data) {
    if (err) 
        console.log(err);
    else {
        data.Items.sort(function(a, b) {
            return parseFloat(a.Created.N) - parseFloat(b.Created.N);
        });
        // More code here
    }
});

Basicamente, sua consulta é semelhante a:

SELECT * FROM TABLE WHERE DataID = "some_id" AND Created > timestamp;

O índice secundário aumentará as unidades de capacidade de leitura / gravação necessárias, portanto, você precisa considerar isso. Ainda é muito melhor do que fazer uma varredura, o que custará leituras e tempo (e é limitado a 100 itens, eu acredito).

Esta pode não ser a melhor maneira de fazer isso, mas para alguém acostumado com RD (também estou acostumado com SQL) é a maneira mais rápida de se tornar produtivo. Como não há restrições em relação ao esquema, você pode preparar algo que funcione e, uma vez que você tenha a largura de banda para trabalhar da maneira mais eficiente, pode mudar as coisas.

ET
fonte
1
Você diz que não há restrições, mas você deve saber que essa abordagem significa que você pode salvar no máximo 10 GB de dados (o máximo de uma única partição).
Ryan Shillington
Essa teria sido a abordagem se DataID fosse conhecido. Mas aqui precisamos obter todas as linhas para as quais o criado é mais do que uma data.
Yasith Prabuddhaka
3

Você poderia transformar a chave Hash em algo semelhante a um id de 'categoria de produto' e, em seguida, a chave de intervalo como uma combinação de um carimbo de data / hora com um id exclusivo anexado no final. Dessa forma, você conhece a chave hash e ainda pode consultar a data com maior que.

Greg
fonte
1

Você pode ter várias chaves hash idênticas; mas apenas se você tiver uma chave de intervalo que varia. Pense nisso como formatos de arquivo; você pode ter 2 arquivos com o mesmo nome na mesma pasta, desde que seu formato seja diferente. Se o formato for o mesmo, o nome deve ser diferente. O mesmo conceito se aplica às chaves hash / range do DynamoDB; pense no hash como o nome e no intervalo como o formato.

Além disso, não me lembro se eles tinham na época do OP (não acredito que tivessem), mas agora eles oferecem índices secundários locais.

Meu entendimento disso é que agora deve permitir que você execute as consultas desejadas sem ter que fazer uma varredura completa. A desvantagem é que esses índices devem ser especificados na criação da tabela e também (eu acredito) não podem ficar em branco ao criar um item. Além disso, eles exigem taxa de transferência adicional (embora normalmente não tanto quanto uma varredura) e armazenamento, portanto, não é uma solução perfeita, mas uma alternativa viável para alguns.

Ainda assim, recomendo a resposta de Mike Brant como o método preferido de usar o DynamoDB; e uso esse método sozinho. No meu caso, tenho apenas uma tabela central com apenas uma chave hash como meu ID, depois as tabelas secundárias que têm um hash e um intervalo que podem ser consultados e o item aponta o código para o "item de interesse" da tabela central, diretamente .

Dados adicionais sobre os índices secundários podem ser encontrados na documentação do DynamoDB da Amazon aqui para os interessados.

De qualquer forma, espero que isso ajude qualquer pessoa que aconteça neste tópico.

DGolberg
fonte
Tentei criar uma tabela DynamoDB onde havia AWSDynamoDBKeySchemaElement 'createdAt' do tipo hash e novamente o AWSDynamoDBKeySchemaElement 'createdAt' do intervalo de tipo e recebi um erro que dizia Error Domain = com.amazonaws.AWSDynamoDBErrorDomain Code = 0 "(null)" UserInfoin Code = 0 "(null) = {__ type = com.amazon.coral.validate # ValidationException, message = Tanto a Hash Key quanto o elemento Range Key no KeySchema têm o mesmo nome}. Portanto, não acho que o que você está dizendo está correto.
user1709076
Eu acredito que você entendeu mal (embora eu suponha que minha descrição também não tenha sido muito clara). Você não pode ter 2 atributos diferentes (colunas) com o mesmo nome, em uma tabela, mas quando você cria uma chave hash com uma chave de intervalo, você pode ter vários itens que usam o mesmo hash, desde que seu intervalo seja diferente, e vise-versa. Por exemplo: Seu hash é "ID" e seu intervalo é "Data", você pode ter 2 instâncias do ID "1234", desde que a data seja diferente.
DGolberg
Ah DGoldberg! Eu entendo você agora. Isso é ótimo. Portanto, no meu caso, uma vez que apenas e sempre desejarei consultar mensagens de texto 'após data = x', parece que eu poderia definir todas as mensagens de texto com o mesmo 'fake_hash = 1'. Em seguida, faça minha query.keyConditionExpression = @ "fake_hash = 1 e #Date>: val". Muito obrigado. Se você tiver alguma outra entrada, ficaria feliz em saber, pois parece estranho ter um hash que sempre tem o mesmo valor.
user1709076
Eu teria que verificar novamente, mas tenho certeza que você pode fazer uma consulta em tabelas apenas de hash ... embora se você estiver usando um carimbo de data / hora como seu hash, eu recomendo gravar até o unidade mais curta possível, como milissegundos ou nano / microssegundos (qualquer que seja a menor unidade de tempo que o código pode gravar), a fim de reduzir a chance de sobreposição de data / hora. Além disso, você pode adicionar bloqueio otimista para reduzir ainda mais a possibilidade de sobreposições: docs.aws.amazon.com/amazondynamodb/latest/developerguide/… Simplesmente tente novamente em outra ocasião se houver um conflito.
DGolberg
-11

Resposta atualizada Não há maneira conveniente de fazer isso usando consultas do Dynamo DB com taxa de transferência previsível. Uma opção (subótima) é usar um GSI com um HashKey & CreatedAt artificial. Em seguida, consulte apenas o HashKey e mencione ScanIndexForward para ordenar os resultados. Se você puder criar um HashKey natural (digamos, a categoria do item, etc.), esse método é o vencedor. Por outro lado, se você mantiver a mesma HashKey para todos os itens, isso afetará a taxa de transferência principalmente quando o conjunto de dados crescer além de 10 GB (uma partição)

Resposta original: Você pode fazer isso agora no DynamoDB usando GSI. Faça o campo "CreatedAt" como um GSI e emita consultas como (GT some_date). Armazene a data como um número (msegs desde a época) para esse tipo de consulta.

Os detalhes estão disponíveis aqui: Índices secundários globais - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

Este é um recurso muito poderoso. Esteja ciente de que a consulta é limitada a (EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN) Condição - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html

Sony Kadavan
fonte
31
Eu votei negativamente porque, pelo que posso dizer, sua resposta está incorreta. Muito parecido com a chave primária de uma tabela, você pode consultar a chave hash de um GSI apenas com o operador EQ. Se você estava sugerindo que CreatedAtdeveria ser a chave de intervalo do GSI, você precisará escolher uma chave hash - e então você está de volta ao ponto de partida, porque será capaz de consultar GT CreatedAtapenas para um valor específico de chave de hash.
PaF
Acordado com PaF. Usar um GSI com a chave hash como hora de criação não ajuda com as perguntas feitas no OP.
4-8-15-16-23-42