← К содержанию навыка

Старт и первые данные

30 минут
Раздел 2 из 8

2. Старт и первые данные

2.1. Локальная разработка и инструменты

Разрабатывать приложение, постоянно дергая реальное облако (AWS) - это плохая практика.

  1. Медленно: Сетевая задержка замедляет тесты.
  2. Дорого: Ошибка в бесконечном цикле записи может стоить вам сотни долларов за час.
  3. Опасно: Случайно запустить DELETE не на той таблице - классика жанра.
  4. Нет интернета: Вы не можете кодить в самолете.

Для комфортной жизни вам нужны два инструмента: DynamoDB Local и NoSQL Workbench.

DynamoDB Local

Это официальная версия базы данных, упакованная в Docker-контейнер (или Java JAR). Она эмулирует API DynamoDB, но работает на вашем ноутбуке, храня данные в оперативной памяти или в локальном файле.

Как запустить (Docker Compose)

Вам не нужно ничего устанавливать, кроме Docker. Добавьте это в ваш docker-compose.yml:

version: '3.8'
services:
  dynamodb-local:
    image: amazon/dynamodb-local:latest
    container_name: dynamodb-local
    ports:
      - "8000:8000"
    # Флаг -sharedDb важен! Без него база создает разные файлы
    # для разных Access Key / Region. С ним - база одна для всех.
    command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ."
    volumes:
      - "./docker/dynamodb:/home/dynamodblocal/db"
    working_dir: /home/dynamodblocal

Как подключить приложение

Это единственное, что нужно изменить в коде. Вам нужно переопределить endpoint_url.

import boto3
import os

# В проде эта переменная пустая, локально - 'http://localhost:8000'
dynamodb_endpoint = os.getenv('DYNAMODB_ENDPOINT')

dynamodb = boto3.resource(
    'dynamodb',
    region_name='us-east-1',
    # Магия здесь:
    endpoint_url=dynamodb_endpoint
)

# Теперь все операции (create_table, put_item) идут в ваш Docker

Ограничения Local-версии:

  • Она не эмулирует всё. Например, Time To Live (TTL) в локальной версии не удаляет данные автоматически (вы должны делать это сами или терпеть).
  • Мониторинга (CloudWatch) нет.
  • Производительность не ограничена WCU/RCU (вы не сможете протестировать троттлинг).

NoSQL Workbench

Если DynamoDB Local - это "движок", то NoSQL Workbench - это "приборная панель". Это бесплатное GUI-приложение от AWS (для Windows/Mac/Linux), которое решает главную проблему Single Table Design: визуализацию.

В голове очень сложно представить, как данные пользователя и заказов лежат в одной таблице вперемешку. Workbench позволяет это нарисовать.

Ключевые возможности:

  1. Data Modeler (визуальное проектирование):

    • Вы создаете схему, определяете PK и SK.
    • Вы добавляете примеры данных.
    • Facets (грани): Это киллер-фича. Вы можете создать "Взгляд со стороны User" (отфильтровать только данные юзера) или "Взгляд со стороны Order". Это помогает проверить, работает ли ваш Single Table Design так, как вы задумали.
  2. Visualizer (визуализация GSI):

    • Вы нажимаете одну кнопку и видите, как ваши данные будут выглядеть во вторичном индексе (GSI).
    • Вы сразу заметите ошибки типа "Ой, я выбрал плохой ключ для индекса, у меня тут перекос данных".
  3. Operation Builder (генератор кода):

    • Вы "накликиваете" сложный запрос (Query с фильтрами, проекциями и условиями) в GUI.
    • Workbench генерирует готовый код на Python, Node.js или Java.
    • Больше не нужно гуглить "как написать KeyConditionExpression boto3". Вы просто копипастите готовый код.

Идеальный рабочий процесс:

  1. Спроектировали таблицу в NoSQL Workbench.
  2. Нажали "Commit to DynamoDB" -> выбрали DynamoDB Local (localhost:8000). Workbench сам создаст таблицу и зальет тестовые данные в ваш Docker.
  3. Написали код и тесты, выполняя их с БД в Docker-контейнере.
  4. Когда все готово - задеплоили в реальный AWS.

Инструменты локальной разработки DynamoDB

Вопросов: 5

2.2. Структура и типы данных

Данные в DynamoDB хранятся в таблицах (Tables). Таблица - это коллекция элементов (Items), а каждый элемент - это набор атрибутов (Attributes). Думайте о таблице как о коллекции JSON-объектов (элементов), где у каждого объекта есть уникальный идентификатор.

Многие разработчики воспринимают DynamoDB просто как "KV-хранилище, куда можно положить JSON". Они делают json.dumps(obj) и сохраняют всё в одно строковое поле. Это ошибка.

DynamoDB - это строго типизированная база данных. Она понимает структуру вашего документа. Если вы используете нативные типы (Map, List, Set), вы получаете суперсилу: частичное обновление и атомарные операции без необходимости читать весь объект.

Все типы делятся на три категории:

Простые типы (Scalar)

Это "атомы" ваших данных.

  • String (S): Обычные строки (UTF-8). Это основной тип для ключей (PK и SK).

    • Важно: DynamoDB не разрешает сохранять пустые строки "". Это частая причина падения кода при миграции с SQL. Если строка пустая, либо не сохраняйте атрибут вообще, либо используйте placeholder (например, "-").
  • Number (N):

    • Особенность: DynamoDB хранит числа как строки. Это сделано, чтобы сохранить точность (precision) до 38 знаков. В JavaScript 0.1 + 0.2 !== 0.3, а в DynamoDB - равно.
    • Математика: Вы можете делать атомарные инкременты. SET views = views + :val. Вам не нужно знать текущее значение просмотров, чтобы увеличить его на 1.
  • Binary (B): Сжатые данные, картинки, криптографические ключи. Хранятся в Base64.

  • Boolean (BOOL): true / false.

  • Null (NULL): Используется редко, так как в NoSQL обычно отсутствие поля и есть null.

Типы документов (Document Types)

Именно они делают DynamoDB документо-ориентированной базой.

  • List (L): Упорядоченный список (массив). Может содержать разные типы.

    • Пример: История статусов заказа [{"status": "NEW", "time": 100}, {"status": "PAID", "time": 200}].
  • Map (M): Вложенный JSON-объект.

    • Пример: Адрес доставки { "city": "Astana", "zip": 010000 }.

Зачем использовать Map вместо JSON-строки? Представьте, что у вас есть огромный профиль пользователя (300 КБ). Пользователь сменил только аватарку.

  1. Подход "JSON-строка": Вы должны прочитать (Read) все 300 КБ, распарсить, поменять поле, запаковать и записать (Write) 300 КБ обратно. Вы платите RCU и много WCU.
  2. Подход "Native Map": Вы делаете UpdateItem, указывая путь к вложенному полю: SET info.avatar = :new_url. DynamoDB сама найдет поле внутри Map и поменяет только его. Это быстрее, дешевле и, главное, не перезатирает изменения других полей, если они произошли параллельно.

Примеры в коде (Python/Boto3)

1. Map vs. JSON String (Почему Map лучше)

Плохой подход (Blob): Вы сохраняете объект как строку. База данных "слепа", она не видит, что внутри.

import json

# Мы "прячем" структуру данных в строку
user_profile = {
    "city": "Astana",
    "theme": "dark",
    "notifications": True
}

table.put_item(
    Item={
        'user_id': 'alice',
        # DynamoDB видит это как одну длинную строку (S)
        'profile_blob': json.dumps(user_profile)
    }
)

# Чтобы изменить тему на 'light', нам нужно:
# 1. GetItem (прочитать всё)
# 2. Распарсить JSON в коде
# 3. Изменить поле
# 4. PutItem (перезаписать ВСЮ строку обратно)
# Это дорого и вызывает Race Conditions.

Правильный подход (Native Map): Вы сохраняете объект как Map. DynamoDB видит структуру и дает доступ к вложенности.

table.put_item(
    Item={
        'user_id': 'alice',
        # DynamoDB видит это как Map (M)
        'profile': {
            "city": "Astana",
            "theme": "dark",
            "notifications": True
        }
    }
)

# ТЕПЕРЬ: Изменяем тему одной командой (Точечное обновление)
table.update_item(
    Key={'user_id': 'alice'},
    # Обращаемся через точку к вложенному полю
    UpdateExpression="SET profile.theme = :t",
    ExpressionAttributeValues={':t': 'light'}
)
# Экономия: Мы не читали данные. Мы отправили байты только для одного поля.

Множества (Sets) - Скрытая жемчужина

Этого типа нет в стандартном JSON. Множество - это коллекция уникальных значений одного типа. Порядок не гарантируется.

  • String Set (SS): Теги, роли, ID друзей.
  • Number Set (NS): ID связанных сущностей.
  • Binary Set (BS).

Зачем использовать Sets вместо List?

  1. Автоматическая дедупликация: Если вы добавите {"admin", "user", "admin"} в Set, DynamoDB сохранит только {"admin", "user"}. List сохранит все дубликаты.
  2. Атомарное удаление по значению: В Set можно удалить элемент по значению (DELETE tags :val). В List - только по индексу (REMOVE tags[2]), что требует предварительного чтения.

Пример: Система тегов

У вас статья, которую редактируют два редактора:

  • Редактор А хочет добавить тег "news".
  • Редактор Б хочет добавить тег "hot".

С List и list_append - атомарное добавление работает:

  1. А делает UpdateItem SET tags = list_append(tags, :new_tag) (где :new_tag = ["news"]).
  2. Б делает UpdateItem SET tags = list_append(tags, :new_tag) (где :new_tag = ["hot"]).

Итог: ["tech", "news", "hot"] - оба тега на месте.

Тогда в чём преимущество Set?

Проблема возникает при удалении:

  • Редактор А хочет удалить тег "news".
  • Редактор Б хочет удалить тег "hot".

С List:

  1. А читает список, находит "news" на индексе 1, делает REMOVE tags[1].
  2. Б читает список, находит "hot" на индексе 2, делает REMOVE tags[2].
  3. Но после удаления А индексы сдвинулись - Б удаляет не тот элемент.

С Set:

  1. А делает DELETE tags :val (где :val = {"news"}).
  2. Б делает DELETE tags :val (где :val = {"hot"}).

Итог: Оба тега корректно удалены, без чтения и без race conditions.

Пример в коде: Магия множеств (Sets) для тегов

Представьте, что два админа одновременно добавляют теги пользователю.

# Сценарий: Добавить тег "senior", не зная, какие теги уже есть.
# Не нужно делать GetItem!

table.update_item(
    Key={'user_id': 'bob'},
    # Оператор ADD работает только с числами и Sets
    UpdateExpression="ADD roles :new_role",
    ExpressionAttributeValues={
        # В Python Boto3 множество передается как set()
        ':new_role': {'senior'}
    }
)

# Если у Боба были роли {'user', 'admin'},
# то теперь станут {'user', 'admin', 'senior'}.
# Дубликаты исключаются автоматически.

Ограничения, о которых нужно знать

  1. Лимит размера элемента - 400 КБ. Это жесткий лимит. Он включает в себя не только значения, но и имена атрибутов.

    • Совет: Если у вас миллионы записей, не называйте поля customer_transaction_date_timestamp. Назовите tx_ts. На миллионе записей это сэкономит гигабайты и деньги.
  2. Вложенность и индексы. DynamoDB не умеет индексировать внутри JSON.

    • Если у вас есть Map address: { city: "London" }, вы не можете создать GSI (вторичный индекс) по полю address.city. Ключом индекса могут быть только скалярные типы верхнего уровня (S, N, B).
    • Решение: Если вам нужно искать по городу, храните city как отдельный атрибут верхнего уровня, а не внутри Map.
  3. Пустые Sets. Множество не может быть пустым. Если вы удалите последний элемент из Set, атрибут исчезнет целиком. При чтении в коде вы должны быть готовы к тому, что поля tags может просто не быть в ответе (KeyError), а не к тому, что оно будет пустым списком [].

Когда выбирать что

КритерийListSet
Нужен порядок элементов
Допустимы дубликаты
Атомарное добавлениеlist_appendADD
Атомарное удаление по значениюDELETE
Элементы - сложные объекты (maps)❌ (только строки/числа/бинарные)

2.3. Первичный ключ

Каждый элемент в таблице должен быть уникально идентифицирован первичным ключом (Primary Key). Это единственный обязательный атрибут для каждого элемента.

Первичный ключ определяет, как данные физически распределяются и как к ним можно обращаться. Он бывает двух типов:

  1. Простой ключ (Simple): Состоит только из одного атрибута - ключа партиции (Partition Key или PK).

    • Техническое определение: DynamoDB использует внутреннюю хэш-функцию для значения PK, чтобы определить, на какой физической партиции (сервере) будет размещен этот элемент.
    • Как работает: Это обеспечивает равномерное распределение данных и операций ввода-вывода по множеству серверов.
    • Ограничение: Позволяет выполнять только прямые операции GetItem (получить один элемент по его PK) или Scan (полное сканирование таблицы). Например, GetItem(user_id='123').
  2. Составной ключ (Composite): Состоит из двух атрибутов: ключа партиции (PK) и ключа сортировки (Sort Key или SK).

    • Техническое определение: Все элементы с одинаковым значением PK физически хранятся вместе на одной партиции. Внутри этой партиции они физически отсортированы в порядке возрастания (по умолчанию) по значению SK.
    • Как работает: PK по-прежнему определяет партицию (куда положить), а SK определяет точный порядок элементов внутри этой партиции.
    • Преимущество: Это открывает мощные диапазонные запросы (Query). Вы можете запросить один элемент по PK и SK, или группу элементов с одним PK и различными SK. Например, Query(PK='user_123') - получить все элементы пользователя 123. Или Query(PK='user_123', SK.begins_with('ORDER#2024')) - получить все заказы пользователя 123, начинающиеся с ORDER#2024.

Это фундаментальная концепция. Выбор PK и SK - это самое важное архитектурное решение, которое вы принимаете при проектировании, поскольку оно напрямую диктует, какие запросы к данным будут эффективными.

Типы данных и первичный ключ в DynamoDB

Вопросов: 6

2.4. Практика

Проектирование структуры данных для конкурента Twitter

Вы продолжаете работу как CTO стартапа-конкурента Twitter. Вместе с Назаром спроектируйте структуру данных для твитов и профилей пользователей в DynamoDB: выберите типы данных для каждого атрибута, определите Primary Key и обсудите, как правильно хранить счётчики лайков и хэштеги.

Назар - ваш персональный ИИ-наставник. Он поможет закрепить материал через практику и ответит на ваши вопросы.

💡 Все обсуждения с ИИ могут быть прочитаны администратором для улучшения качества обучения.