Desde Junho/2017 o DynamoDB passou a contar com a capacidade de Auto-Scaling, liberando administradores da complexa e custosa tarefa de ajustar manualmente o throughput das tabelas no DynamoDB. O trecho abaixo foi copiado da página da AWS e explica como o processo funciona.

Você cria uma política do Aplicativo Auto Scaling para a sua tabela do DynamoDB.
O DynamoDB publica métricas de capacidade consumida no Amazon CloudWatch.
Se a capacidade consumida da tabela exceder sua utilização prevista (ou cair abaixo desse alvo) por um período específico, o Amazon CloudWatch disparará um alarme. Você verá o alarme no Console de gerenciamento da AWS e receberá notificações usando o Amazon Simple Notification Service (Amazon SNS).
O alarme do CloudWatch chama o Aplicativo Auto Scaling para avaliar sua política de escalabilidade.
O Aplicativo Auto Scaling emite uma solicitação UpdateTable para ajustar o throughput provisionado da sua tabela.
O DynamoDB processa a solicitação UpdateTable, aumentando (ou diminuindo) dinamicamente a capacidade de throughput provisionado da tabela de forma que ela se aproxime da sua utilização prevista.
O problema é que na prática este processo é mais lento do que faz crer. De fato são necessários 5 minutos consecutivos de aumento de demanda para que o auto-scale entre em ação, ajustando RCUs e/ou WCUs de acordo com o volume de requisições, ou quase.
Alguns workarounds propostos amenizam o problema, como no excelente artigo publicado por Yan Cui, mas o desenvolvedor também precisa preocupar-se com o devido tratamento das exceções devolvidas pelo DynamoDB, especialmente as que reportam problemas com o provisionamento de WCUs e RCUs.
Calculando WCU e RCUs
Primeiro vamos rever alguns conceitos importantes, começando com o cálculo de RCU (Read Capacity Units).
Eventually consistent: Uma leitura logo após uma operação de gravação do mesmo item pode não recuperar o dado persistido, o qual só estará disponível algum tempo depois. A vantagem é que o custo cai pela metade.
Strongly consistent: O DynamoDB retornará o dado mais atualizado, mas o custo será o dobro de uma leitura Eventually Consistent.
A unit of read capacity: Representa uma leitura por segundo (strongly consistent) ou duas leituras por segundo(eventually consistent) para itens de até 4KiB.
Uma vez determinado se a leitura utilizará strong ou eventually consistent reading, a seguinte fórmula pode ser aplicada:
Tamanho do registro (arredondado para o próximo múltiplo de 4) / 4) * número de itens. Exemplo:Tamanho da estrutura JSON - 5.5KiB = 8KB
Tipo de leitura - Eventually consistent
Número de leituras simultâneas - 10
8 / 4 * 10 = 20 RCUs
Como estamos usando Eventually consistent read, dividimos 20 por 2 (cada Capacity Unit usando Eventually Consistent equivale a duas leituras) e achamos o total de 10 RCUs. Se a leitura for Strongly Consistent, então o valor seria 20 RCUs.O cálculo de WCU (Write Capacity Unit) é mais simples. Cada WCU equivale a 1KiB. Multiplica-se o tamanho do registro pelo número de gravações concorrentes para determinar o o número de WCUs.
Ainda falando sobre conceitos é bom lembrar que o DynamoDB provê capacidade de burst, mas ela é limitada a 5 minutos de carga, desde que não ultrapasse o capacity total provisionado.

Provisionamento não consumido de outras partições é usado na partição com excesso de carga.
Tratando exceções
Do ponto de vista da aplicação, a forma como a API do DynamoDB é consumida precisa prover uma forma de repetir uma operação que falhou. Isto possui impacto no código, mas fornece uma segurança adicional para o caso de falha por causa da falta de provisionamento.
Em processos batch pode-se fazer uso do recurso de DLQ (Dead LetterQueue), mas quando lidamos com processos online (uma API sendo consumida por uma app ou webapp, por exemplo) ainda que a resposta inicialmente demore um pouco mais, ela precisa ser fornecida para o usuário (tipo, listar os posts numa timeline, não queremos que o usuário receba uma mensagem do tipo "Tente novamente mais tarde").
Tipos de exceções
Basicamente existem dois tipos de exceção:
4xx - Exceções causadas por problemas na origem, ou seja, ou os parâmetros estão errados, falta autorização para execução da API, tipo de condicional está errada etc.
5xx - Exceções causadas por falta de provisionamento, excesso de concorrência etc
Apenas exceções do tipo 5xx podem ser repetidas até que sejam atendidas pelo DynamoDB.
A seguir temos a lista completa de exceções geradas pela API do DynamoDB.
Message: Access denied.
The client did not correctly sign the request. If you are using an AWS SDK, requests are signed for you automatically; otherwise, go to the Signature Version 4 Signing Process in the AWS General Reference.
OK to retry? No
Message: The conditional request failed.
You specified a condition that evaluated to false. For example, you might have tried to perform a conditional update on an item, but the actual value of the attribute did not match the expected value in the condition.
OK to retry? No
Message: The request signature does not conform to AWS standards.
The request signature did not include all of the required components. If you are using an AWS SDK, requests are signed for you automatically; otherwise, go to the Signature Version 4 Signing Process in the AWS General Reference.
OK to retry? No
Message: Collection size exceeded.
For a table with a local secondary index, a group of items with the same partition key value has exceeded the maximum size limit of 10 GB. For more information on item collections, see Item Collections.
OK to retry? Yes
Message: Too many operations for a given subscriber.
There are too many concurrent control plane operations. The cumulative number of tables and indexes in the CREATING, DELETING or UPDATING state cannot exceed 10.
OK to retry? Yes
Message: Request must contain a valid (registered) AWS Access Key ID.
The request did not include the required authorization header, or it was malformed. See DynamoDB Low-Level API.
OK to retry? No
Message: You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. To view performance metrics for provisioned throughput vs. consumed throughput, open the Amazon CloudWatch console.
Example: Your request rate is too high. The AWS SDKs for DynamoDB automatically retry requests that receive this exception. Your request is eventually successful, unless your retry queue is too large to finish. Reduce the frequency of requests, using Error Retries and Exponential Backoff.
OK to retry? Yes
Message: The resource which you are attempting to change is in use.
Example: You tried to recreate an existing table, or delete a table currently in the CREATING state.
OK to retry? No
Message: Requested resource not found.
Example: Table which is being requested does not exist, or is too early in the CREATING state.
OK to retry? No
Message: Rate of requests exceeds the allowed throughput.
This exception might be returned if you perform any of the following operations too rapidly: CreateTable; UpdateTable; DeleteTable.
OK to retry? Yes
Message: The Access Key ID or security token is invalid.
The request signature is incorrect. The most likely cause is an invalid AWS access key ID or secret key.
OK to retry? Yes
Message: Varies, depending upon the specific error(s) encountered
This error can occur for several reasons, such as a required parameter that is missing, a value that is out range, or mismatched data types. The error message contains details about the specific part of the request that caused the error.
OK to retry? No
An HTTP 5xx status code indicates a problem that must be resolved by Amazon Web Services. This might be a transient error in which case you can retry your request until it succeeds. Otherwise, go to the AWS Service Health Dashboard to see if there are any operational issues with the service.
DynamoDB could not process your request.
OK to retry? Yes
Note
You may encounter Internal Server Errors while working with items. These are expected during the lifetime of a table. Any failed requests can be retried immediately.
DynamoDB is currently unavailable. (This should be a temporary state.)
OK to retry? Yes
Código antes
O código abaixo é perfeitamente válido, mas totalmente inútil em situação de alta concorrência pois não trata as possíveis exceções de provisionamento:
const AWS = require('aws-sdk'); const docClient = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1' }); exports.handler = (event, context, callback) => { const done = (err, res) => callback(null, { statusCode: err ? '400' : '200', body: err ? err.message : JSON.stringify(res), headers: { 'Content-Type': 'application/json', }, }); var stage = event.context.stage; var query = event.params.querystring var contentId = query.content_id if (typeof contentId !== "undefined" && query.itemsLimit !== "" && query.sortOrder !== "") { let params = { TableName: `mytable-cms-content-${stage}`, KeyConditionExpression: "id = :contentId", FilterExpression: "#st = :contentStatus", Limit: query.itemsLimit, ScanIndexForward: query.sortOrder, ExpressionAttributeNames: { "#st": "status" }, ExpressionAttributeValues: { ":contentStatus": "Published", ":contentId": contentId } } docClient.query(params, done); } else { done (400, "Parâmetros incorretos!") } }; |
O mesmo código, funcionalmente falando, mas com o tratamento necessário para as exceções de provisionamento:
const AWS = require('aws-sdk'); const docClient = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1' }); exports.handler = (event, context, callback) => { var stage = event.context.stage; var query = event.params.querystring var contentId = query.content_id if (typeof contentId !== "undefined" && query.itemsLimit !== "" && query.sortOrder !== "") { let params = { TableName: `mytable-cms-content-${stage}`, KeyConditionExpression: "id = :contentId", FilterExpression: "#st = :contentStatus", Limit: query.itemsLimit, ScanIndexForward: query.sortOrder, ExpressionAttributeNames: { "#st": "status" }, ExpressionAttributeValues: { ":contentStatus": "Published", ":contentId": contentId } } var repeatQuery = () => { docClient.query(params).promise().then((data) => { return callback(null, data) }).catch((err) => { if(err.code=='ProvisionedThroughputExceededException'){ console.log('Unable to execute update - function execUpdate - ProvisionedThroughputExceededException'); repeatQuery(); } if(err.code=='LimitExceededException'){ console.log('Unable to execute update - function execUpdate - LimitExceededException'); repeatQuery(); } if(err.code=='ThrottlingException'){ console.log('Unable to execute update - function execUpdate - ThrottlingException'); repeatQuery(); } if(err.code=='UnrecognizedClientException'){ console.log('Unable to execute update - function execUpdate - UnrecognizedClientException'); repeatQuery(); } if(err.statusCode >=500){ console.log('Unable to execute update - function execUpdate - Server Error 5xx'); repeatQuery(); } // Qualquer outro erro, retorne o mesmo return callback(400, err) }); } }; |
Exponential backoff
Ajustando o código de acordo com o exemplo acima tem o efeito de fazer com que o script gaste mais tempo processando enquanto o DynamoDB não dá uma resposta positiva. Por sua vez, o DynamoDB implementa um dispositivo de segurança para automaticamente repetir o request chamado de "exponential backoff". Isto implica num aumento exponencial do tempo de resposta do DynamoDB a cada vez que ele não conseguiu dar uma resposta satisfatória para o client. Este valor pode ser ajustado, usando a seguinte configuração:
var docClient = new AWS.DynamoDB.DocumentClient({maxRetries: 5, retryDelayOptions: {base: 300},region: 'us-east-1' });O parâmetro "base" define o tempo entre repetições (iniciando em 300 e dobrando a cada repetição (600, 1200, 2400 etc).
O parâmetro "maxRetries" indica a quantidade de repetições que o DynamoDB realizará antes de desistir.
Estes parâmetros são importantes para que se tenha em mente que o timeout da função Lambda deve ser capaz de lidar com eventuais atrasos no processamento. Caso a função tenha um timeout curto a sua execução será interrompida independentemente do tratamento do erro no código.
Considerações finais
Definir um ou mais boilerplates para a criação de funções Lambda ajuda a padronizar questões como estas, padronizando melhores práticas na criação de código tolerante a falhas e capaz de gerenciar workloads mais robustos, ao mesmo tempo em que minimiza o investimento em infraestrutura ao dar capacidade do DynamoDB de realizar o auto-scale sem prejuízo das transações em andamento.
Emerson Lopes
Arquiteto de soluções na nuvem
emersonlopes@gmail.com