Для получения более детальной информации по архитектуре хранилища читайте нашу серию статей Mastering the Datastore.
Введение в Python Datastore API
В Python сущности хранилища создаются из объектов Python; атрибуты объекта становятся свойствами сущности. Для создания новой сущности вы вызываете базовый класс родительской сущности (если таковая имеется), устанавливает атрибуты объектов, а затем сохраняет объект (вызывая такой метод как put()).
import datetime
from google.appengine.ext import db
from google.appengine.api import users
class Employee(db.Model):
name = db.StringProperty(required=True)
role = db.StringProperty(required=True, choices=set(["executive", "manager", "producer"]))
hire_date = db.DateProperty()
new_hire_training_completed = db.BooleanProperty(indexed=False)
account = db.UserProperty()
e = Employee(name="John",
role="manager",
account=users.get_current_user())
e.hire_date = datetime.datetime.now().date()
e.put()
API хранилища предоставляет два интерфейса для запросов: интерфейс объектных запросов и похожий на SQL язык запросов, называемый GQL. Запрос возвращает сущности в форме экземпляра объекта класса модели, который можно изменить и обратно поместить в хранилище:
training_registration_list = [users.User("Alfred.Smith@example.com"),
users.User("jharrison@example.com"),
users.User("budnelson@example.com")]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE account IN :1",
training_registration_list)
for e in employees_trained:
e.new_hire_training_completed = True
db.put(e)
Сущности
Объекты в хранилище App Engine известны как сущности. Сущность имеет одно или несколько именованных свойств, каждое из которых может иметь одно или несколько значений. Значения свойств могут быть данными различного типа, включая целые числа, числа с плавающей запятой, строки, даты, бинарные данные и многие другие. Запрос по свойству с множеством значений проверяет, соответствует ли какое-нибудь из значений нужному критерию запроса. Такие свойства очень удобны при проверке совокупности значений.
Примечание: сущности хранилища безсхемные, как в традиционных реляционных БД, хранилище App Engine не требует, чтобы все сущности одного типа имели одинаковый набор свойств или чтобы все значения свойств сущности были одинакового типа данных. Если потребуется формальная схема. то само приложение ответственно за то, чтобы сущности соответствовали ей; Python SDK включает богатую библиотеку возможностей моделирования данных для этого.
Типы, ключи и идентификаторы
Каждая сущность хранилища имеет свой тип или вид, который классифицирует сущность по назначению запроса: например, приложение управления человеческими ресурсами может представлять каждого работника в компании с помощью сущности типа Employee. Также, каждая сущность имеет собственный ключ, который однозначно ее идентифицирует. Ключ состоит из следующих компонентов:
Тип сущности
Опциональный родительский путь определяющий сущность в иерархии хранилища
Идентификатор, который может быть:
ключевой строкой
целым числом ID
Идентификатор ассоциируется с сущностью при ее создании. Так как он является частью ключа сущности, он ассоциируется с сущностью навсегда и не может изменяться. Он может быть присвоен двумя путями:
Ваше приложение может само указать ключевую строку-значение для сущности
Вы можете определить для хранилища автоматическое присвоение идентификатору численное значение ID
Примечание: вместо именованных строковых значений или генерирования числовых значений ID автоматически, сложные приложения могут иногда присваивать собственные числовые значения ID сущностям, которые они создают. Будьте внимательны, если вы выберите этот путь, вам придется предпринять специальные меры, чтобы предотвратить возможные конфликты вручную присвоенных ID с автоматически-сгенерированными хранилищем, смотрите Сущности, свойства и Ключи для подробной информации.
Родительские пути
Сущности в хранилище формируют иерархическую структуру, похожую на структуру директорий файловой системы. При создании сущности вы опционально можете указать другую сущность как родительскую; новая сущность будет потомком родительской сущности. Эта связь между сущностью и ее родителем неразрывна и ее нельзя будет изменить после ее создания. Сущность у которой нет родителя является корневой (root) сущностью. Хранилище никогда не присвоит двум сущностям с одинаковым родителем одинаковый ID, также как и двум корневым сущностям.
Родитель сущности, родитель родителя и так далее по рекурсии являются предками. Ее наследники, потомки потомков называются потомками. Сущность и ее потомки принято называть одной группой потомков. Последовательность сущностей, начиная с корневой и следую от родителя к потомкам до указанной сущности составляю родительский путь или путь предков сущности. Полный ключ, идентифицирующий сущность состоит из последовательности пар идентификатор-тип, указывающих их родительский путь и заканчивающихся на самой сущности:
Person:GreatGrandpa / Person:Grandpa / Person:Dad / Person:Me
У корневой сущности родительский путь пуст и ключ состоит только из собственно типа и идентификатора.
Person:GreatGrandpa
Запросы и индексы
Кроме прямого получения сущностей из хранилища по их ключам, приложение может выполнять запрос для их получения по значениям свойств. Запрос работает с сущностями указанного типа, он может фильтровать сущности по значениям свойств или ключей и может возвращать ноль и более сущностей в результате. (Для экономии памяти и увеличения производительности запросу следует где возможно указывать лимит на количество результирующих данных). Запросу также можно указать порядок сортировки по значениям свойств. Результат включает все сущности, которые имеют хотя бы одно (возможно нулевое) значение для каждого свойства, указанного в фильтре и порядке сортировки и свойства которых соответствуют указанному в фильтре критерию. Запрос может возвращать всю сущность, ее проекцию или только ключи.
Запрос может также включать родительский фильтр, ограничивающий результаты только группой потомков указанного родителя. Такие запросы известны также как родительские запросы. По-умолчанию родительские запросы возвращают строго соответствующие результаты, которые гарантировано синхронизированы с последними изменениями в данных. Неродительские запросы напротив, могут охватывать все хранилище, а не только одну группу сущностей, которые в конечном счете соответствуют реальным, но могут возвращать устаревшие данные. Если приложению важно строгое соответствие, то вам необходимо учесть это при структурировании данных, размещая связанные сущности в той же группе сущностей, так что их можно будет получить с помощью родительского а не обычного запроса; смотрите для подробностей Структурирование данных для точной согласованности.
Помимо фильтров в запросе можно указать порядок сортировки по значениям свойств. Результаты запроса будут включать все сущности, которые имеют по крайней мере одно (возможно нулевое) значение для каждого свойства, указанного в фильтре и порядке сортировки и чьи свойства соответствуют указанным критериям фильтра. Запрос может возвращать целиком сущности, указанное подмножество их свойств или просто их ключи (в ключевых запросах).
Примечание: для экономии памяти и увеличения производительности запросы следует где только возможно ограничивать по количеству выдаваемых результатов.
Каждый запрос использует индекс - таблицу, содержащую потенциальные результаты запроса в необходимом порядке. Хранилище обновляет индексы постепенно для отражения любых изменений, которые приложение делает с сущностями. Таким образом, корректные результаты всех запросов мгновенно доступны напрямую из индексов, без необходимости проведения дополнительных расчетов.
Индексы для некоторых типов запросов предоставляются автоматически; приложение может определить дополнительные индексы для себя в файле индексной конфигурации под названием index.yaml. Вэб сервер разработчика автоматически добавляет предложения в конфигурационный файл, когда находит запросы, у которых еще не сконфигурированы индексы. Вы можете вручную настроить индексы, отредактировав файл перед загрузкой приложения, смотрите статью Выбор индексов и расширенный поиск для дополнительной информации.
Примечание: Этот индексный механизм поддерживает широкий спектр запросов и подходит для большинства приложений. Однако он не поддерживает некоторые типы запросов, общие для других технологий БД, в частности объединение и запросы агрегирования не поддерживаются в это механизме запросов.
Транзакции
Каждая попытка создать, обновить или удалить сущность происходит в контексте транзакции. Отдельная транзакция может содержать любое количество таких операций. Для поддержки целостности данных, транзакция проходит если все ее операции применены к хранилищу успешно как блок или если хоть одна не завершилась успешно, отменяется весь блок операций.
Вы можете выполнить множество действий с сущностью внутри одной транзакции. Например для увеличения поля счетчика объекта вам необходимо считать значение счетчика, рассчитать новое значение и сохранить его обратно. Без использования транзакции возможна ситуация, когда другой процесс изменит значение поля счетчика в промежуток времени между тем, когда вы его прочитали и моментом обновления счетчика и ваше приложение перезапишет это значение своим.Использование транзакций обеспечит уверенность в том, что никакой другой процесс в это время не повлияет на данные.
Транзакции и группы сущностей
Внутри транзакции разрешены только родительские запросы, а это значит что каждый запрос ограничен одной отдельной группой сущностей. Однако сама по себе транзакция может применяться к множеству сущностей, которые могут как принадлежать к отдельной группе (в случае кросс-групповых транзакций) так и включать до пяти различных групп сущностей.
Хранилище использует оптимистичный параллелизм для управления транзакциями. Когда два и более экземпляров приложения пытаются изменить одну группу сущностей в один момент времени (как обновляя сущности так и создавая новые), первое приложение пытающееся внести изменение сможет это сделать, а другие нет. Эти другие приложения могут позже попытаться еще раз внести изменения в уже обновленные первым приложением данные. Помните, что это ограничивает количество параллельных записей, которые вы можете сделать в любую сущность в данной группе сущностей.
Кросс-групповые транзакции
Транзакция с сущностями, принадлежащими к разным группам, называется кросс-групповая (XG) транзакция. В пределах одной транзакции можно работать максимум с пятью группами сущностей и будет завершена успешно, если ни одна из групп не будет затронута другими параллельными транзакциями.
В транзакции с одиночной группой вы не можете выполнять неродительские запросы в XG транзакции. Однако вы можете выполнять родительские запросы на отдельных группах сущностей. Нетранзакционные (неродительские) запросы могут видеть все, некоторые или никакие результаты предыдущих выполненных транзакций (подробнее об этом в Запись в хранилище и видимость данных). Однако такие нетранзакционные запросы лучше всего подходят, чтобы увидеть результаты частично выполненной XG транзакции, чем такие же частично выполненные одиночные групповые транзакции.
Примечание: первая попытка чтения группы сущностей при XG запросах может вызвать исключение TransactionFailedError, если есть конфликт с другой транзакцией, работающей с этой группой сущностей в этот момент. Это означает, что даже XG транзакция, пытающаяся просто считать данные потерпит неудачу и вызовет исключение согласованности данных.
XG транзакция, которая затрагивает только одну группу сущностей ведет себя также как транзакция с одиночной группой. Операции в такой транзакции имеют такую же производительность и стоимость как и эквивалентная одиночно-групповая транзакция с экономией ресурсов и биллинга, но с более высокой задержкой.
Запись в хранилище и видимость данных.
Данные записываются в хранилище в две фазы:
1. В фазе фиксации (Commit) данные сущности записываются в журнал
2. Фаза применения состоит из двух параллельных действий:
Операция записи возвращается сразу после фазы Commit а фаза применения происходит асинхронно. Если произошла ошибка при первом шаге Commit, то он автоматически повторяется, однако если ошибки продолжаются, хранилище возвращает сообщение об ошибке, которое ваше приложение принимает как исключение. Если Commit фаза проходит, а фаза применения вызывает ошибку то она переносится вперед до завершения пока не произойдет следующее:
Периодическая зачистка хранилища на предмет незавершенных Commit операций и их применение
Некоторые операции применения (get, put, delete и родительские запросы) которые используют затронутые группы сущностей не будут выполнятся пока не будут завершены предыдущие предыдущие.