Старт и первые данные
2. Старт и первые данные
2.1. Локальная разработка и инструменты
Разрабатывать приложение, постоянно дергая реальное облако (AWS) - это плохая практика.
- Медленно: Сетевая задержка замедляет тесты.
- Дорого: Ошибка в бесконечном цикле записи может стоить вам сотни долларов за час.
- Опасно: Случайно запустить
DELETEне на той таблице - классика жанра. - Нет интернета: Вы не можете кодить в самолете.
Для комфортной жизни вам нужны два инструмента: 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 позволяет это нарисовать.
Ключевые возможности:
-
Data Modeler (визуальное проектирование):
- Вы создаете схему, определяете PK и SK.
- Вы добавляете примеры данных.
- Facets (грани): Это киллер-фича. Вы можете создать "Взгляд со стороны User" (отфильтровать только данные юзера) или "Взгляд со стороны Order". Это помогает проверить, работает ли ваш Single Table Design так, как вы задумали.
-
Visualizer (визуализация GSI):
- Вы нажимаете одну кнопку и видите, как ваши данные будут выглядеть во вторичном индексе (GSI).
- Вы сразу заметите ошибки типа "Ой, я выбрал плохой ключ для индекса, у меня тут перекос данных".
-
Operation Builder (генератор кода):
- Вы "накликиваете" сложный запрос (
Queryс фильтрами, проекциями и условиями) в GUI. - Workbench генерирует готовый код на Python, Node.js или Java.
- Больше не нужно гуглить "как написать KeyConditionExpression boto3". Вы просто копипастите готовый код.
- Вы "накликиваете" сложный запрос (
Идеальный рабочий процесс:
- Спроектировали таблицу в NoSQL Workbench.
- Нажали "Commit to DynamoDB" -> выбрали DynamoDB Local (localhost:8000). Workbench сам создаст таблицу и зальет тестовые данные в ваш Docker.
- Написали код и тесты, выполняя их с БД в Docker-контейнере.
- Когда все готово - задеплоили в реальный 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 (например,"-").
- Важно: DynamoDB не разрешает сохранять пустые строки
-
Number (N):
- Особенность: DynamoDB хранит числа как строки. Это сделано, чтобы сохранить точность (precision) до 38 знаков. В JavaScript
0.1 + 0.2 !== 0.3, а в DynamoDB - равно. - Математика: Вы можете делать атомарные инкременты.
SET views = views + :val. Вам не нужно знать текущее значение просмотров, чтобы увеличить его на 1.
- Особенность: DynamoDB хранит числа как строки. Это сделано, чтобы сохранить точность (precision) до 38 знаков. В JavaScript
-
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 КБ). Пользователь сменил только аватарку.
- Подход "JSON-строка": Вы должны прочитать (Read) все 300 КБ, распарсить, поменять поле, запаковать и записать (Write) 300 КБ обратно. Вы платите RCU и много WCU.
- Подход "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?
- Автоматическая дедупликация: Если вы добавите
{"admin", "user", "admin"}вSet, DynamoDB сохранит только{"admin", "user"}. List сохранит все дубликаты. - Атомарное удаление по значению: В Set можно удалить элемент по значению (
DELETE tags :val). В List - только по индексу (REMOVE tags[2]), что требует предварительного чтения.
Пример: Система тегов
У вас статья, которую редактируют два редактора:
- Редактор А хочет добавить тег "news".
- Редактор Б хочет добавить тег "hot".
С List и list_append - атомарное добавление работает:
- А делает
UpdateItem SET tags = list_append(tags, :new_tag)(где:new_tag = ["news"]). - Б делает
UpdateItem SET tags = list_append(tags, :new_tag)(где:new_tag = ["hot"]).
Итог: ["tech", "news", "hot"] - оба тега на месте.
Тогда в чём преимущество Set?
Проблема возникает при удалении:
- Редактор А хочет удалить тег "news".
- Редактор Б хочет удалить тег "hot".
С List:
- А читает список, находит "news" на индексе 1, делает
REMOVE tags[1]. - Б читает список, находит "hot" на индексе 2, делает
REMOVE tags[2]. - Но после удаления А индексы сдвинулись - Б удаляет не тот элемент.
С Set:
- А делает
DELETE tags :val(где:val = {"news"}). - Б делает
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'}.
# Дубликаты исключаются автоматически.
Ограничения, о которых нужно знать
-
Лимит размера элемента - 400 КБ. Это жесткий лимит. Он включает в себя не только значения, но и имена атрибутов.
- Совет: Если у вас миллионы записей, не называйте поля
customer_transaction_date_timestamp. Назовитеtx_ts. На миллионе записей это сэкономит гигабайты и деньги.
- Совет: Если у вас миллионы записей, не называйте поля
-
Вложенность и индексы. DynamoDB не умеет индексировать внутри JSON.
- Если у вас есть
Mapaddress: { city: "London" }, вы не можете создать GSI (вторичный индекс) по полюaddress.city. Ключом индекса могут быть только скалярные типы верхнего уровня (S,N,B). - Решение: Если вам нужно искать по городу, храните
cityкак отдельный атрибут верхнего уровня, а не внутриMap.
- Если у вас есть
-
Пустые Sets. Множество не может быть пустым. Если вы удалите последний элемент из
Set, атрибут исчезнет целиком. При чтении в коде вы должны быть готовы к тому, что поляtagsможет просто не быть в ответе (KeyError), а не к тому, что оно будет пустым списком[].
Когда выбирать что
| Критерий | List | Set |
|---|---|---|
| Нужен порядок элементов | ✅ | ❌ |
| Допустимы дубликаты | ✅ | ❌ |
| Атомарное добавление | ✅ list_append | ✅ ADD |
| Атомарное удаление по значению | ❌ | ✅ DELETE |
| Элементы - сложные объекты (maps) | ✅ | ❌ (только строки/числа/бинарные) |
2.3. Первичный ключ
Каждый элемент в таблице должен быть уникально идентифицирован первичным ключом (Primary Key). Это единственный обязательный атрибут для каждого элемента.
Первичный ключ определяет, как данные физически распределяются и как к ним можно обращаться. Он бывает двух типов:
-
Простой ключ (Simple): Состоит только из одного атрибута - ключа партиции (Partition Key или PK).
- Техническое определение: DynamoDB использует внутреннюю хэш-функцию для значения PK, чтобы определить, на какой физической партиции (сервере) будет размещен этот элемент.
- Как работает: Это обеспечивает равномерное распределение данных и операций ввода-вывода по множеству серверов.
- Ограничение: Позволяет выполнять только прямые операции
GetItem(получить один элемент по его PK) илиScan(полное сканирование таблицы). Например,GetItem(user_id='123').
-
Составной ключ (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 и обсудите, как правильно хранить счётчики лайков и хэштеги.
Назар - ваш персональный ИИ-наставник. Он поможет закрепить материал через практику и ответит на ваши вопросы.
💡 Все обсуждения с ИИ могут быть прочитаны администратором для улучшения качества обучения.