Дизайн базы данных - Google App Engine

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

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

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

  • Назначения
  • Позиции
  • Счета-фактуры
  • платежи

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

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

Имя: Цена: Комиссия: Оценка времени   
Полная детализация, обычный размер:        160       75       3,5 часа 
Скидка до 10 $ на купон:       -10        0         0 часов 
Премиум Деталь:                   220      110       4,5 часа 
Производные итоги (не позиция): $370     $185       8,0 часов

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

Оплата: "Оплата" - это запись того, какие платежи поступили.

В предыдущей реализации этого приложения жизнь была проще, и я рассматривал все четыре из этих понятий как одну таблицу в базе данных 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...

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

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

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

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

Спасибо!

Разъяснение использования

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

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

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

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

  • Read Manager загружает календарь
  • Read Manager загружает встречу для клиента, которому он хочет позвонить
  • Менеджер записи нажимает кнопку "Позвонить", вызов инициируется и записывается новый объект CallReacord
  • Сервер Read Call отвечает на запрос вызова и читает CallRecord, чтобы узнать, как обрабатывать вызов
  • Сервер Write Call записывает обновленную информацию в CallRecord
  • Запись при закрытии звонка, сервер звонков отправляет серверу другой запрос на обновление ресурса CallRecord (примечание: этот запрос не является критичным по времени)

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

3 ответа

Решение

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

  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 - ваш друг - используйте его для кэширования результатов запросов к хранилищам данных (или даже результатов более высокого уровня), чтобы вам не приходилось пересчитывать его с нуля при каждом доступе.

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам