Дизайн базы данных - Google App Engine - PullRequest
7 голосов
/ 25 июня 2010

Я работаю с Google App Engine и использую Java-API низкого уровня для доступа к Big Table.Я создаю приложение SAAS с 4 слоями:

  • Клиентский веб-браузер
  • Уровень ресурсов RESTful
  • Бизнес-уровень
  • Уровень доступа к данным

Я создаю приложение, которое поможет мне управлять моей компанией, специализирующейся на автообработке мобильных телефонов (и другим, как она).Я должен представить эти четыре отдельные концепции, но не уверен, что мой текущий план хорош:

  • Назначения
  • Позиции
  • Счета
  • Платежи

Назначение: «Назначение» - это место и время, когда сотрудники должны находиться для оказания услуги.

Позиция: «Позиция» - это услуга, плата или скидка и связанная с ней информация.Пример позиций, которые могут попасть на встречу:

Name:                          Price: Commission: Time estimate   
Full Detail, Regular Size:        160       75       3.5 hours 
$10 Off Full Detail Coupon:       -10        0         0 hours 
Premium Detail:                   220      110       4.5 hours 
Derived totals(not a line item): $370     $185       8.0 hours

Счет-фактура: Счет-фактура - это запись одной или нескольких позиций, за которые клиент обязался оплатить.

Платеж: «Платеж» - это запись о поступлении платежей.

В предыдущей реализации этого приложения жизнь была проще, и я лечил всечетыре из этих понятий как одна таблица в базе данных SQL: «Назначение».Одна «Встреча» может иметь несколько позиций, несколько платежей и один счет.Счет-фактура представлял собой просто электронное письмо или распечатку, созданную из позиций и записей клиентов.

9 из 10 раз, это работало нормальноКогда один клиент записался на прием на один или несколько автомобилей и оплатил их самостоятельно, все было грандиозно.Но эта система не работала во многих условиях.Например:

  • Когда один клиент назначил одну встречу, но на полпути назначил встречу, в результате чего на следующий день пришлось вернуться, мне потребовались две встречи, но только одна позиция,один счет и один платеж.
  • Когда группа клиентов в офисе решила сделать все свои машины в один и тот же день, чтобы получить скидку, мне потребовалась одна встреча, но несколько счетов и несколько платежей.
  • Когда один клиент заплатил за две встречи одним чеком, мне потребовалось две встречи, но только один счет и один платеж.

Я смог справиться со всеми этими выбросами, выдумывая вещинемного.Например, если один из сотрудников должен был вернуться на следующий день, я бы просто назначил еще одну встречу на второй день с позицией с надписью "Готово", а стоимость составила бы 0 долларов.Или, если бы у меня был один клиент, заплативший за два приема с одним чеком, я бы поместил разделенные записи о платежах в каждое назначение.Проблема в том, что это создает огромную возможность для несоответствия данных.Несоответствие данных может быть серьезной проблемой, особенно для случаев, связанных с финансовой информацией, такой как третий пример, когда клиент оплатил две встречи с одним чеком.Платежи должны быть сопоставлены непосредственно с товарами и услугами, предоставляемыми для надлежащего отслеживания дебиторской задолженности.

Предлагаемая структура:

Ниже приведена нормализованная структура для организации и хранения этих данных. Возможно, из-за моей неопытности я уделяю большое внимание нормализации данных, потому что это кажется отличным способом избежать ошибок несоответствия данных. С этой структурой изменения данных могут быть сделаны с одной операцией, не заботясь об обновлении других таблиц. Однако для чтения может потребоваться многократное чтение в сочетании с организацией данных в памяти. Позже я пойму, что если есть проблемы с производительностью, я могу добавить некоторые денормализованные поля в «Назначение» для более быстрого запроса, сохраняя при этом «безопасную» нормализованную структуру нетронутой. Денормализация потенциально может замедлить запись, но я подумал, что смогу сделать асинхронные вызовы к другим ресурсам или добавить в очередь задач, чтобы клиенту не приходилось ждать дополнительных записей, которые обновляют денормализованные части данных. .

Таблицы:

Appointment
 start_time
 etc...

Invoice
 due_date
 etc...

Payment
 invoice_Key_List
 amount_paid
 etc...

Line_Item
 appointment_Key_List
 invoice_Key
 name
 price
 etc...

Ниже приведен ряд запросов и операций, необходимых для связывания всех четырех сущностей (таблиц) для заданного списка встреч. Это будет включать информацию о том, какие услуги были запланированы для каждой встречи, общую стоимость каждой встречи и погоду или не оплату, как было получено для каждой встречи. Это будет обычный запрос при загрузке календаря для планирования встреч или для менеджера, чтобы получить общее представление об операциях.

  • QUERY для списка "Встречи", чье поле "start_time" находится между заданным диапазоном.
    • Добавьте каждый ключ из возвращенных встреч в Список.
  • QUERY для всех "Line_Items", чье поле assign_key_List включает в себя любые возвращаемые встречи
    • Добавьте каждый invoice_key из всех позиций в коллекцию Set.
  • QUERY для всех «Счета-фактуры» в наборе счетов-фактур (это можно сделать за одну асинхронную операцию с использованием механизма приложений)
    • Добавить каждый ключ из возвращенных счетов в Список
  • QUERY для всех "Платежей", поле invoice_key_list которого содержит ключ, соответствующий любому из возвращенных счетов
  • Реорганизовать в памяти так, чтобы каждое назначение отражало запланированные для него строки line_items, общую стоимость, общее расчетное время и погоду или за нее не было оплачено.

... Как видите, для этой операции требуется 4 запроса к хранилищу данных, а также некоторая организация в оперативной памяти (надеюсь, оперативная память будет довольно быстрой)

Кто-нибудь может прокомментировать этот дизайн? Это лучшее, что я могу придумать, но я подозреваю, что могут быть лучшие варианты или совершенно другие конструкции, о которых я не думаю, что они могли бы работать лучше в целом или конкретно в силу сильных и слабых сторон, возможностей и возможностей GAE (Google App Engine). .

Спасибо!

Уточнение использования

Большинство приложений более интенсивно читают, некоторые более интенсивно пишут. Ниже я опишу типичный пример использования и операции разбивки, которые пользователь хотел бы выполнить:

Менеджер получает звонок от клиента:

  • Чтение - Менеджер загружает календарь и ищет доступное время
  • Запись - Менеджер запрашивает у клиента информацию, я представлял ее как последовательность асинхронных операций чтения, когда менеджер вводит каждый фрагмент информации, такой как номер телефона, имя, адрес электронной почты, адрес и т. Д. ... Или, если необходимо, возможно, одна запись в конце после того, как клиентское приложение собрало всю информацию, и оно затем было отправлено.
  • Запись - Менеджер снимает данные кредитной карты клиента и добавляет ее к своей записи в качестве отдельной операции
  • Запись - Менеджер снимает средства с кредитной карты и проверяет, что платеж прошел

Менеджер делает исходящий телефонный звонок:

  • Чтение Менеджер загружает календарь
  • Чтение Менеджер загружает встречу для клиента, которому он хочет позвонить
  • Запись Менеджер нажимает кнопку «Вызов», инициируется вызов и создается новая сущность CallReacordзаписано
  • Чтение Сервер вызовов отвечает на запрос вызова и читает CallRecord, чтобы выяснить, как обрабатывать вызов
  • Запись Сервер вызовов записывает обновленную информациюв CallRecord
  • Запись , когда вызов закрыт, сервер вызовов делает еще один запрос к серверу на обновление ресурса CallRecord (примечание: этот запрос не является критичным по времени)

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

Ответы [ 3 ]

9 голосов
/ 25 июня 2010

Вы указали два конкретных "просмотра", которые должен предоставить ваш сайт:

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

  2. Общий вид операций. Я не совсем уверен, что это влечет за собой, но если вам нужно выполнить строку из четырех запросов, которые вы упомянули выше, чтобы получить это, то ваш дизайн может использовать некоторые улучшения. Подробности ниже.

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

  1. Получение списка встреч - не проблема. Этот запрос сможет сканировать индекс для эффективного извлечения встреч в указанном вами диапазоне дат.

  2. Получить все позиции для каждой встречи из # 1 - это проблема. Этот запрос требует выполнения запроса IN. IN запросов преобразуются в N подзапросов за кулисами - так что в итоге вы получите один запрос на ключ назначения из # 1! Они будут выполняться параллельно, так что это не так уж плохо. Основная проблема заключается в том, что запросы IN ограничены небольшим списком значений (до 30 значений). Если у вас более 30 ключей назначения, возвращенных # 1, этот запрос не будет выполнен!

  3. Получить все счета, на которые ссылаются позиции - без проблем. Вы правы, что этот запрос дешевый, потому что вы можете просто получить все соответствующие счета напрямую по ключу. (Примечание: этот запрос все еще синхронный - я не думаю, что слово, которое вы искали, было асинхронным).

  4. Получить все платежи по всем счетам, возвращаемым # 3 - это проблема. Как и # 2, этот запрос будет IN запросом и не будет выполнен, если # 3 вернет даже умеренное количество счетов, за которые вам нужно получить платежи.

Если количество элементов, возвращаемых # 1 и # 3, достаточно мало, то GAE почти наверняка сможет сделать это в допустимых пределах. И этого должно быть достаточно для ваших личных потребностей - похоже, вам больше всего нужно, чтобы оно работало, и не нужно, чтобы оно масштабировалось до огромного количества пользователей (это не будет).

Предложения по улучшению:

  • Денормализация! Попробуйте сохранить ключи для объектов Line_Item, Invoice и Payment, относящихся к данному назначению, в списках на самом назначении. Затем вы можете устранить ваши IN запросы. Убедитесь, что эти новые ListProperty не проиндексированы, чтобы избежать проблем с взрывающимися индексами

Другие, менее конкретные идеи для улучшения:

  • В зависимости от того, что будет показывать ваш «общий вид операций», вы можете разделить поиск всей этой информации. Например, возможно, вы начинаете с показа списка встреч, а затем, когда менеджеру требуется дополнительная информация о конкретной встрече, вы продолжаете и получаете информацию, относящуюся к этой встрече. Вы могли бы даже сделать это через AJAX, если бы это взаимодействие происходило на одной странице.
  • Memcache - ваш друг - используйте его для кэширования результатов запросов к хранилищам данных (или даже результатов более высокого уровня), чтобы вам не приходилось пересчитывать его с нуля при каждом доступе.
7 голосов
/ 25 июня 2010

Как вы заметили, этот дизайн не масштабируется. Для отображения страницы требуется 4 (!!!) запроса к БД. Это 3 слишком много:)

Преобладающей идеей работы с хранилищем данных App Engine является то, что вы хотите выполнять как можно больше работы, когда что-то пишется, так что практически ничего не нужно делать, когда что-то извлекается и отображается. Предположительно, вы пишете данные очень мало раз по сравнению с тем, сколько раз они отображались.

Нормализация - это то же самое, к чему вы стремитесь. Хранилище данных не помещает никаких значений в нормализацию - это может означать меньшее несоответствие данных, но это также означает, что чтение данных происходит намного медленнее (4 чтения? !!). Поскольку ваши данные читаются гораздо чаще, чем пишутся, оптимизируйте их для чтения, даже если это означает, что ваши данные будут иногда дублироваться или не синхронизироваться в течение короткого периода времени.

Вместо того, чтобы думать о том, как выглядят данные, когда они хранятся, подумайте о том, как вы хотите, чтобы данные выглядели, когда они отображаются пользователю. Храните как можно ближе к этому формату, даже если это означает буквальное хранение предварительно отрендеренного HTML в хранилище данных. Чтение будет молниеносным, и это хорошо.

Так что, поскольку вы должны оптимизировать чтение, часто ваши записи увеличиваются до гигантских размеров. Настолько гигантский, что вы не можете уместить его в течение 30 секунд для запросов. Ну, вот для чего нужна очередь задач . Сохраните то, что вы считаете «необходимыми» для вашей модели, в хранилище данных, затем запустите очередь задач, чтобы вытащить ее обратно, сгенерировать HTML-код для рендеринга и поместить его там в фоновом режиме. Это может означать, что ваша модель сразу готова к отображению до тех пор, пока задача не будет завершена, поэтому в этом случае вам потребуется постепенное ухудшение, даже если это означает, что она будет отображаться «медленным путем» до полного заполнения данных. Любые дальнейшие чтения будут молниеносными.

Таким образом, у меня нет конкретных советов, непосредственно связанных с вашей базой данных - это зависит от того, как вы хотите, чтобы данные выглядели, когда пользователь их видит.

Что я могу дать вам несколько ссылок на несколько очень полезных видео о хранилище данных:

  • Бретт Слаткин 2008 и 2009 рассказывает о создании масштабируемых, сложных приложений на App Engine, а отличный из в этом году о конвейерах данных (что не Я думаю, что это не совсем применимо, но в целом полезно)
  • App Engine под прикрытием : как App Engine делает то, что делает, за кулисами
  • AppStats : отличный способ узнать, сколько операций чтения из хранилища данных вы выполняете, и несколько советов по уменьшению этого числа
2 голосов
/ 25 июня 2010

Вот несколько факторов, специфичных для движка приложений, с которыми, я думаю, вам придется бороться:

  • При запросах с использованием неравенства вы можете использовать неравенство только для одного свойства . Например, если вы фильтруете по дате апплета между 1 и 4 июля, вы также не можете фильтровать по price > 200

  • Транзакции на ядре приложения немного сложнее по сравнению с базой данных SQL, к которой вы, вероятно, привыкли. Транзакции можно выполнять только с объектами, которые находятся в одной « группе объектов ».

...