DynamoDB para noobs como yo

10 segundos de fama

  • Andrés Santos 
  • Co-Organizador 
  • Senior Software Engineer                                 
    • Backend Node + AWS

@dresrok

@EpamAnywhere

@LibertyMutual

@IbaguéJS

Antes de empezar...

  • Tipos de Bases de Datos
  • Teorema CAP

Tipos de Bases de Datos

Fuente: Graph Databases in Action - Dave Bechberger & Josh Perryman

Teorema CAP

Los sistemas distribuidos no pueden garantizar a la vez que haya Consistencia, Disponibilidad y Tolerancia a Particiones.

Consistency: Todos los nodos ven los mismos datos simultáneamente. En cada lectura se retorna la escritura más reciente.

Teorema CAP

Availability: Cada cliente SIEMPRE puede leer y escribir. En el caso de las lecturas el valor puede que no sea el más reciente.

Teorema CAP

Partition Tolerance: El sistema continúa operando a pesar de fallas de en red.

Teorema CAP

Fuente: Bases de Datos para el Big Data - Marlon Cardenas

Danos el código

Oblíguenme

Empecemos...

  • ¿Qué es DynamoDB?
  • ¿Cómo funciona?
  • ¿Cuándo no debe usarse?
  • Conceptos Generales
  • DynamoDB API

¿Qué es DynamoDB?

  • ​NoSQL rápida y flexible (key-value, schemaless)
  • Operaciones de lectura y escritura en ms
  • Escalamiento infinito sin degradación del rendimiento
  • Totalmente gestionada
  • Ofrece API, Autenticación e Infraestructura como código (IaC)

¿Cómo funciona?

Primary Key = Partition Key

+ Sort Key

¿Cuándo no debe usarse?

  • Cuando tu aplicación necesite queries avanzados, joins o agrupaciones.
  • Para obtener rankings, donde se necesiten funciones de agregación y clasificación 
  • Cuando se requiere análisis en tiempo real de datos históricos

Conceptos Generales

  • Tablas

  • Atributos*

  • Items

  • Llaves Primarias*

  • Índices**

  • Streams**

  • Time-to-live (TTL)**

Conceptos Generales

Fuente: The DynamoDB Book - Alex DeBrie

Conceptos Generales

Atributos*​​

  • Scalars: Representan exactamente un valor.

    • string, number, binary, boolean y null

  • Documents: Representan una estructura compleja con valores anidados similar a un JSON.

    • list y map

  • Sets: Representan grupos de valores escalares.

    • string, number y binary

Conceptos Generales

// Scalars
{
  "Name": {
    "S": "Andrés Santos"
  },
  "Picture": {
    "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"
  },
  "Age": {
    "NULL": true
  },
  "IsOrganizer": {
    "BOOL": true
  },
  "Attendees": {
    "N": "100.0"
  }
}
// Documents
{
  "Event": {
    "M": {
      "Name": {
        "S": "IbaguéJS - Meetup Octubre"
      },
      "Date": {
        "S": "2022-10-22T10:00:00+0500"
      },
      "Topics": {
        "L": [
          {"S": "DynamoDB para noobs como yo"},
          {"S": "Redux Toolkit"}
        ]
      }
    }
  }
}
// Sets
{
  "Organizers": {
    "SS": ["Diana", "Yeison", "Juan JS", "Jomazao"]
  },
  "AvgAttendance": {
    "NS": ["42.2", "19", "35", "32.14"]
  },
  "EventImages": {
    "BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]
  }
}

Conceptos Generales

Llaves Primarias*​​

  • Simples: Consta de un solo elemento llamado Partition Key (Hash) cuyo valor debe ser único.

  • Compuestas: Consta de dos elementos el Partition Key y el Sort Key (Range), el valor del PK puede repetirse pero la combinación de ambos genera un identificador único.

Conceptos Básicos

Llaves Primarias*​​

Conceptos Básicos

// Usando SDK
const AWS = require('aws-sdk');
AWS.config.update({region: 'REGION'});

const ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
const params = {
  AttributeDefinitions: [
    { AttributeName: 'Actor', AttributeType: 'S' },
    { AttributeName: 'Movie', AttributeType: 'S' }
  ],
  KeySchema: [
    { AttributeName: 'Actor', KeyType: 'HASH' },
    { AttributeName: 'Movie', KeyType: 'RANGE' }
  ],
  TableName: 'actors-dev',
};

ddb.createTable(params, function(err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Table Created", data);
  }
});

Creando una tabla

// Usando CDK
import dynamodb = require('aws-cdk-lib/aws-dynamodb');

const dbTable = new dynamodb.Table(this, 'actors-dev', {
  tableName: 'actors-dev',
  partitionKey: { name: 'Actor', type: 'S' },
  sortKey: { name: 'Movie', type: 'S' },
});

Conceptos Básicos

Índices**​​

  • Local Secondary Index (LSI): Tiene el mismo PK que la tabla base pero un SK diferente.

  • Global Secondary index (GSI): Tanto el PK como el SK son diferentes que la tabla base.

Conceptos Básicos

Streams**​​

Fuente: The DynamoDB Book - Alex DeBrie

Conceptos Básicos

Time-to-live (TTL)**​​

  • Permite definir un tiempo de expiración para borrar un item de manera automática.

// Usando CDK
import dynamodb = require('aws-cdk-lib/aws-dynamodb');

const dbTable = new dynamodb.Table(this, 'actors-dev', {
  tableName: 'actors-dev',
  partitionKey: { name: 'Actor', type: 'S' },
  sortKey: { name: 'Movie', type: 'S' },
  timeToLiveAttribute: 'ExpiresAt', // Unix Timestamp
});

DynamoDB API

  • ​PutItem
  • GetItem
  • UpdateItem
  • DeleteItem
  • Query
  • Scan

PutItem

// PutItem

INSERT INTO "actors-dev" (Actor, Movie, Role, ...)
VALUES (Natalie Portman, Star Wars: Attack of the Clones, Padmé Amidala, ...);

import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  Item: {
    Actor: { S: 'Natalie Portman' },
    Movie: { S: 'Star Wars: Attack of the Clones' },
    Role: { S: 'Padmé Amidala' },
    Year: { N: '2002' },
    Genre: { SS: ['Epic', 'Space Opera', 'Fantasy'] },
    CharacterAttributes: {
      M: {
        HairColor: { S: 'brown'},
        SkinColor: { S: 'light'},
        EyeColor: {S: 'brown'},
      }
    }
  },
};
await new DynamoDB().putItem(params).promise();

GetItem

// PK + SK

SELECT * FROM "actors-dev"
WHERE "Actor" = 'Tom Hanks' AND "Movie" = 'Toy Story'

import { DynamoDB } from 'aws-sdk';
const params = {
    TableName: 'actors-dev',
    Key: {
      Actor: { S: 'Tom Hanks' },
      Movie: { S: 'Toy Story' },
    },
    ConsistentRead: true,
};

const result = await new DynamoDB().getItem(params).promise();

result.Item.Role.S // Woody
// PK

SELECT * FROM "actors-dev"
WHERE "ActorID" = 'b261bcc9-3a3f-4ef2-b62f-85311e9f474e'

import { DynamoDB } from 'aws-sdk';
const params = {
    TableName: 'actors-dev',
    Key: {
      ActorID: { S: 'b261bcc9-3a3f-4ef2-b62f-85311e9f474e' }
    },
    ConsistentRead: true,
};

const result = await new DynamoDB().getItem(params).promise();

result.Item.Role.S // Woody

UpdateItem

import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  Key: {
    Actor: { S: 'Natalie Portman' },
    Movie: { S: 'Star Wars: Attack of the Clones' },
  },
  UpdateExpression:
    'SET #year = :year, #characterAttributes.#eyeColor :eyeColor",
  	ADD #picture :picture"',
  ExpressionAttributeNames: {
      '#year': 'Year',
      "#characterAttributes": "CharacterAttributes",
      "#eyeColor": "EyeColor",
      "#picture": "Picture"
  },
  ExpressionAttributeValues: {
      ':year': { N: '2002' },
      ':eyeColor': { S: 'green' },
      ':picture': { B: 'cGljdHJl=' }
  }
}

await new DynamoDB().updateItem(params).promise()
import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  Key: {
    Actor: { S: 'Tom Hanks' },
    Movie: { S: 'Cast Away' },
  },
  UpdateExpression: 'ADD #awards :awards"',
  ExpressionAttributeNames: {
      '#awards': 'Awards',
  },
  ExpressionAttributeValues: {
      ':awards': {
        SS: [
          'Golden Globe Award for Best Actor',
          'People\'s Choice Award for Favorite Movie Actor'
        ]
      }
  }
}

await new DynamoDB().updateItem(params).promise()
import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  Key: {
    Actor: { S: 'Natalie Portman' },
    Movie: { S: 'Star Wars: Attack of the Clones' },
  },
  UpdateExpression: 'REMOVE #picture :picture"',
  ExpressionAttributeNames: {
      '#picture': 'Picture',
  },
}

await new DynamoDB().updateItem(params).promise()

DeleteItem

// PK + SK

DELETE FROM "actors-dev" 
WHERE "Actor" = 'Tim Allen' AND "Movie" = 'Toy Story'

import { DynamoDB } from 'aws-sdk';
const params = {
    TableName: 'actors-dev',
    Key: {
      Actor: { S: 'Tim Allen' },
      Movie: { S: 'Toy Story' },
    },
};

await new DynamoDB().deleteItem(params).promise();
// PK

DELETE FROM "actors-dev" 
WHERE "ActorID" = '1a0847bc-ae37-4977-ba3d-d400b4109926'

import { DynamoDB } from 'aws-sdk';
const params = {
    TableName: 'actors-dev',
    Key: {
      ActorID: { S: '1a0847bc-ae37-4977-ba3d-d400b4109926' },
};

await new DynamoDB().deleteItem(params).promise();

Query

SELECT * FROM "actors-dev" 
WHERE "Actor" LIKE 'Tom Hanks'
LIMIT 2

import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  KeyConditionExpression: '#actor = :actor',
  ExpressionAttributeNames: {
    '#actor': 'Actor'
  },
  ExpressionAttributeValues: {
    ':actor': { 'S': 'Tom Hanks' }
  },
  Limit: 2
}
const result = await new DynamoDB().query(params).promise();
result.Items.forEach((item) => item.Role.S)
import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  KeyConditionExpression: '#actor = :actor AND #movie BETWEEN :a AND :m',
  ExpressionAttributeNames: {
    '#actor': 'Actor',
    '#movie': 'Movie'
  },
  ExpressionAttributeValues: {
    ':actor': { 'S': 'Tom Hanks' },
    ':a': { 'S': 'A' },
    ':m': { 'S': 'M' }
  },
}

await new DynamoDB().query(params).promise();
result.Items.forEach((item) => item.Role.S)
import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  KeyConditionExpression: '#actor = :actor AND begins_with(#movie, :movie)',
  ExpressionAttributeNames: {
    '#actor': 'Actor',
    '#movie': 'Movie'
  },
  ExpressionAttributeValues: {
    ':actor': { 'S': 'Tom Hanks' },
    ':movie': { 'S': 'Cast' }
  },
}

await new DynamoDB().query(params).promise();
result.Items.forEach((item) => item.Role.S)
// Key condition expressions for query
a = b
a < b
a <= b
a > b
a >= b
a BETWEEN b AND c
begins_with (a, substr)

Query + GSI

import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  IndexName: 'MoviesIndex',
  KeyConditionExpression: '#movie = :movie',
  ExpressionAttributeNames: {
    '#movie': 'Movie'
  },
  ExpressionAttributeValues: {
    ':movie': { 'S': 'Toy Story' }
  },
}

await new DynamoDB().query(params).promise();
result.Items.forEach((item) => item.Actor.S)
// Usando CDK
import dynamodb = require('aws-cdk-lib/aws-dynamodb');

const dbTable = new dynamodb.Table(this, 'actors-dev', {
  tableName: 'actors-dev',
  partitionKey: { name: 'Actor', type: 'S' },
  sortKey: { name: 'Movie', type: 'S' },
});

dbTable.addGlobalSecondaryIndex({
  indexName: 'MoviesIndex',
  partitionKey: { name: 'Movie', type: 'S'},
  sortKey: { name: 'Actor', type: 'S'},
  projectionType: 'ALL',
});

Query + Filter

import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'customer-orders-dev',
  KeyConditionExpression: '#c = :c AND #ot BETWEEN :start and :end"',
  FilterExpression: '#amount > :amount',
  ExpressionAttributeNames: {
    '#c': 'CustomerId',
    '#ot': 'OrderTime',
    "#amount": "Amount"
  },
  ExpressionAttributeValues: {
    ':c': { 'S': '36ab55a589e4' },
    ':start': { 'S': '2020-01-10T00:00:00.000000' },
    ':end': { 'S': '2020-01-20T00:00:00.000000' },
    ":amount": { "N": "80" }
  },
}

await new DynamoDB().query(params).promise();

Scan

import { DynamoDB } from 'aws-sdk';
const params = {
  TableName: 'actors-dev',
  FilterExpression: "#genre = :genre",
  ExpressionAttributeNames: {
    '#genre': 'Genre',
  },
  ExpressionAttributeValues: {
    ":genre": { "S": "Drama" }
  },
}

await new DynamoDB().scan(params).promise();

DynamoDB

By Andrés Santos

DynamoDB

  • 153