BugzillaORM — различия между версиями

Материал из YourcmcWiki
Перейти к: навигация, поиск
м (Идеи по созданию объектного ядра)
м (Идеи по созданию объектного ядра)
Строка 54: Строка 54:
 
* Поле содержит ссылку на другое поле, в которое должна попадать ссылка на объект, на который ссылается объект, на который ссылается поле +))
 
* Поле содержит ссылку на другое поле, в которое должна попадать ссылка на объект, на который ссылается объект, на который ссылается поле +))
  
Но тут появляется следующий вопрос: это что, всегда во все объекты тащить все связи объектов, с которыми они связаны («замыкать»)? No way! А чо делать?
+
Но тут появляется следующий вопрос: это что, всегда во все объекты тащить все связи объектов, с которыми они связаны («замыкать»)? No way!
 +
 
 +
Соответственно, окончательная идея — «зависимости» между полями связанных объектов навешиваются в виде чего-то типа «внешних ключей». Например:
 +
* Объект «баг», поля «продукт» и «компонент», ссылающиеся на объекты «продукт» и «компонент»
 +
* У объекта «компонент» есть поле «продукт», ссылающееся на объект «продукт»
 +
* На баг навешено дополнительное ограничение: баг.компонент.продукт == баг.продукт.
 +
 
 +
Возможно, так даже получится сделать зависимость поля от двух других!
 +
 
 +
А видимость полей других типов — например, строковых — пусть контролируется так же, как и сейчас.
  
 
== Текущее состояние ==
 
== Текущее состояние ==

Версия 01:59, 11 марта 2012

В Bugzilla половина кода в шаблонах, половина на основе полу-ORM’а Bugzilla::Object. Большая часть — говнокод. С одной стороны — ORM бы туда, было бы классно, НО! ORM мало, и нужен не он! Все (существующие) ORM-движки — это просто объектный интерфейс к базе данных. А нужно некое объектное ядро, которое бы позволяло создавать свои объекты, с полями различных типов, в том числе и ссылающимися на другие такие же объекты, с возможностью приписывания специальных особенностей полю, с общим механизмом хранения истории и с автоматическим базовым CRUD-интерфейсом.

На самом деле, выше описан почти что Roundup, за теми исключениями, что он не умеет зависимые селекты (раз) и  :)

Идеи по созданию объектного ядра

Объекты в Bugzilla:

  • баг
  • классификация, продукт, компонент
  • тег (ключевое слово / keyword), milestone, версия, статус бага, agreement (пример кастомного поля)
  • вложение, комментарий, флаг, тип флага
  • пользователь, группа
  • если таки будет ядро — ещё появляется метакласс сущности, объекты которого описывают типы сущностей

[svg]

Связь «многие ко многим» багов с ключевыми словами можно разбить на две «один ко многим» (тег → тег бага ← баг).

Причём, понятное дело, если баг принадлежит к некоторому продукту (через компонент), то и его версия, milestone, agreement должны принадлежать к тому же продукту. Сейчас в Bugzilla это реализуется так — для каждой сущности, связанной с багом, в баге есть «поле», а для ограничения значений полей поля делаются «зависимыми» от других полей. Из-за этого там куча костылей и, по сути, повесить зависимость на отличное от поля «продукт» поле невозможно.

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

Несколько ссылок на один тип
Всё это ведь не означает, что баг должен иметь только одну ссылку, скажем, на версию. Он вполне может иметь две ссылки — например, версия, в которой нашли и версия, в которой зафиксили. Последняя — это, по сути, target milestone, но ведь это не значит, что мы не можем захотеть ссылаться на сущность того же типа «версия». Или другой пример: двухуровневая техподдержка, ссылка на связанный внешний баг и ссылка на связанный внешний продукт из продукта. В конечном итоге, это можно решить присвоением «имени» ссылке, по умолчанию равному названию типа сущности, на которую ссылаемся. Это, кстати, снова становится похоже на схему с зависимыми полями :)
Права
Сейчас в Bugzilla они рулятся на уровне продукта. TODO
Скрытие/показ полей бага в зависимости друг от друга
Сейчас в Bugzilla это реализовано отдельно от функционала контроля значений — по сути, скрываться выпадающее поле может в зависимости от одного поля, а набор значений может при этом меняться в зависимости от другого. А на самом деле это неправильно! Что за бред — поле скрыто, но при этом есть варианты, которые можно выбрать? Или наоборот — поле доступно, но из вариантов — только пустое значение. Не подходит. Соответственно, контроль показа поля в том виде, в котором он есть в Bugzilla сейчас, актуален только для полей других типов — строковых и так далее.
Email-уведомления об изменениях
С ними более-менее всё понятно. По сути, они всегда привязываются к какому-либо полю какого-либо объекта общей структуры, ссылающемуся на пользователя/пользователей (например, Assignee и CC бага) — это со стороны ссылок на пользователей. Вторая сторона — событие, но единственно возможные события — изменение поля или создание новой сущности.

А во-вторых — если не хранить поле (например, продукт), от которого зависит другое поле (например, компонент), в самом баге, тоже полезут костыли (только другие). Соответственно, нужно понять, чем вообще схема, реализованная в самой багзилле сейчас, плоха? А вот чем:

  • Значения кастом полей в таблице багов хранятся не ссылками по ID, а по именам, что создаёт геморрой для зависимых полей, так как чтобы точно идентифицировать значение, приходится брать ещё и значение поля, от которого оно зависит.
  • «Поле» (селект и мультиселект) совмещено с объектом, соответственно, не добавишь дополнительных атрибутов объекту.
  • Для выпадающих списков есть как «контроль значений», так и «контроль видимости поля», что в корне неверно (см. выше).
  • По факту, ни от чего, кроме продукта, зависимым значение не сделаешь.
  • История хранится только для багов, но не для остальных объектов.
  • Поля не генерятся автоматически, а ручками вписываются в шаблоны.

Учитывая, что всё равно нужно вычислять «непротиворечивость» зависимостей полей, появляется следующая мысль:

  • Разделяем «поля» и «объекты».
  • Объекты могут содержат ссылки на другие (компонент ← продукт).
  • Поле содержит ссылку на другое поле, в которое должна попадать ссылка на объект, на который ссылается объект, на который ссылается поле +))

Но тут появляется следующий вопрос: это что, всегда во все объекты тащить все связи объектов, с которыми они связаны («замыкать»)? No way!

Соответственно, окончательная идея — «зависимости» между полями связанных объектов навешиваются в виде чего-то типа «внешних ключей». Например:

  • Объект «баг», поля «продукт» и «компонент», ссылающиеся на объекты «продукт» и «компонент»
  • У объекта «компонент» есть поле «продукт», ссылающееся на объект «продукт»
  • На баг навешено дополнительное ограничение: баг.компонент.продукт == баг.продукт.

Возможно, так даже получится сделать зависимость поля от двух других!

А видимость полей других типов — например, строковых — пусть контролируется так же, как и сейчас.

Текущее состояние

Базовые поля (то, чего не быть логически не может):

  • Объект, баг, пользователь, группа, комментарий к багу.
  • Продукт имеет специальный смысл — разграничение прав. Поэтому он тоже обязателен.

У объектов есть:

  • ID — первичный ключ
  • Поля
  • Ссылка на родителя (ID, тип задаётся в классе сущности). Сущности образуют дерево.
  • Способ преобразования в строку

Логическая неконсистентность:

  • Объект «компонент» имеет поле «продукт», тип которого — ссылка на объект «продукт»
  • Чтобы отказаться от дурацкой системы «контролирующих значений» и сделать дерево, в объекте «баг» самостоятельного поля «продукт» быть не должно
  • Но как тогда выбирать сначала продукт, а потом компонент, при редактировании бага?
  • Получается, что «продукт» — это просто способ подразделения компонентов

Ещё одна логическая неконсистентность:

  • Если мы не хотим завязываться на существование поля «компонент», но у нас всё равно «дерево», получается, что в случае отсутствия поля «компонент» баг привязывается напрямую к продукту

И ещё одна:

  • Если «продукт» — это всего лишь способ уточнения компонентов, то как же ограничивать им другие поля?

У каждого поля есть:

  • Тип
  • Значение по умолчанию
  • Вид показа в интерфейсе (по умолчанию?)
  • Копируется ли значение атрибута при клонировании
  • Показывается ли атрибут в форме создания
  • Ссылка на контролирующий атрибут того же объекта, что и этот, и на его значения
    Если выбрано, означает, что атрибут показывается, только если другой атрибут той же сущности, которой принадлежит этот, имеет одно из заданных значений

Типы атрибутов:

  • Ok16.png Строка
  • Boolean
  • Ok16.png Десятичное число
  • Дата
  • Время
  • Ok16.png Дата+время
  • Файл (вложение)
  • Single-Select → ссылка на сущность (то есть «многие к 1»)
  • Multi-Select → ссылка на несколько сущностей одного типа (то есть «многие ко многим»)
    • Два способа показа — поле со списком (комбобокс), или мультиселект
  • Список тегов, с автодобавлением значений

Что ушло бы в этот типа ORM ?

  • Custom Fields, то есть, управление полями багов.
  • Отключение стандартных полей типа OS, Hardware.
  • Добавление спецполей в продукты, компоненты и т. д. — сейчас это делается в коде.
  • Версионирование всех объектов. История бы хранилась унифицированно. Не было бы отдельно таблиц bugs_activity и longdescs, соответственно не было бы и тормозов при проверке «Only bugs changed between…» либо комментарии бы дублировались в bugs_activity (логичнее)
  • Простые интерфейсы типа CRUD (Create/Read/Update/Delete), сейчас созданные непонятно каким копипастом. В основном имеется ввиду админка.
  • Права на редактирование объектов.
  • Валидаторы и подсказки значений.
  • Корректная валидация зависимостей полей друг от друга.
  • Часть SQL-запросов, написанных ручками в коде.
  • Вся логика постановки/обновления багов из process_bug и post_bug, дублированная сейчас в обработке входящих e-mailов и Excel-импорте
  • Даже Excel- и XML-импорт, причём импортировать можно было бы вообще всё что угодно.

Появились бы дополнительные возможности:

  • Создание новых объектов типа SCRUM карточек.
  • Изменение типов стандартных полей типа целочисленных приоритетов.
  • Можно было бы прикрутить статистику по любым объектам, причём с Time Machine, то есть просмотром статистики за любой прошедший момент времени. :)

Существующие поля

Поля багов

Поля багов перечислены ниже. Жирное «да» в колонке «можно отключить» означает, что отключать можно уже сейчас (скорее всего, через параметры типа usevotes и т. п.). Нежирное «да» в колонках «можно отключить» и «можно менять тип» означают, что чисто теоретически логика работы Bugzilla это позволяет.

поле тип можно отключить? можно менять тип? примечания
short_desc строка нет нет
classification single-select да нет
product single-select да нет контролируется classification (или никем), у поля есть привязка к правам пользователя
component single-select да нет контролируется product
version single-select да да контролируется product, если тип = single-select. значение по умолчанию контролируется component
rep_platform single-select да да специальная функция «угадывания» дефолтного значения
bug_file_loc строка да да
op_sys single-select да да специальная функция «угадывания» дефолтного значения
bug_status single-select нет нет есть функция валидации (Bug Status Workflow)
resolution single-select да нет у атрибута есть контролирующая видимость сущность (показывается только при bug_status.closed=1)
status_whiteboard строка да да
keywords multi-select да да показ в виде списка
bug_severity single-select да нет
priority single-select да да есть желание сделать decimal
assigned_to single-select нет нет ссылка на пользователя. значение по умолчанию контролируется component
reporter single-select нет нет ссылка на пользователя
qa_contact single-select да нет ссылка на пользователя. значение по умолчанию контролируется component
votes decimal(,0) да нет
cc multi-select да нет ссылка на пользователей. значение по умолчанию контролируется component
dependson multi-select да нет ссылка на баги. показ в виде списка
blocked multi-select да нет ссылка на баги. показ в виде списка
target_milestone single-select да да контролируется product, если тип = single-select
see_also multi-select да да ссылка на баги. показ в виде списка (багов), если тип = single-select
alias строка да да
reporter_accessible boolean нет нет
cclist_accessible boolean нет нет
estimated_time время да нет
remaining_time время да нет
deadline дата да нет
creation_ts дата+время нет нет
delta_ts дата+время нет нет
cf_agreement single-select да да контролируется product
*** нет в fielddescs ***
lastdiffed дата+время нет нет скрыто в интерфейсе
attachments multi-select нет нет 1 ко многим
longdescs multi-select нет нет 1 ко многим
flags multi-select да нет 1 ко многим

Примечания:

  • Все поля-ссылки на пользователей показываются в виде строки либо в виде combo-box’а, и у них есть функция валидации — она угадывает юзеров по некорректным именам.

Логически минимальный набор полей

Совершенно точно, никогда и ни при каких условиях у бага не могут отсутствовать поля:

  • ID
  • Заголовок
  • История => reporter, время создания (creation_ts), время изменения (delta_ts), время оповещения по почте (lastdiffed)
  • Комментарии

Без этого никакой «баг» смысла не имеет ни в одном баг-трекере.

Устаревшие поля

Устаревшие поля / поля, которые ХЗ зачем нужны в таблице полей багов:

  • assignee_accessible
  • qacontact_accessible
  • longdesc
  • commenter
  • longdescs.isprivate
  • content
  • bug_group
  • flagtypes.name
  • requestees.login_name
  • setters.login_name

Вычисляемые поля

Вычисляемые поля багов:

work_time Сумма work_time от связанных longdescs
percentage_complete (Сумма work_time от dependson)/(Сумма estimated_time от dependson)
owner_idle_time Текущая дата минус MAX(дата последнего коммента от Assignee, дата последней активности от Assignee)
days_elapsed Текущая дата минус delta_ts
everconfirmed Менялся ли статус хоть раз на != UNCONFIRMED

Поля вложений

submitter single-select, показ в виде select’а или строки, есть функция валидации (угадывания юзера по некорректному имени)
description строка
filename строка
mimetype строка
ispatch boolean
isobsolete boolean
isprivate boolean
isurl boolean
thedata потенциально строка, а вообще-то обычно NULL, так как данные хранятся в локальных файлах

Поля компонентов

поле тип можно отключить? можно менять тип? примечания
name строка нет нет
initialowner single-select да нет ссылка на пользователя
initialqacontact single-select да нет ссылка на пользователя
initialcc multi-select да нет ссылка на пользователей
default_version single-select да нет ссылка на версию
description строка да да
product_id single-select нет нет
wiki_url строка да нет
is_active boolean да нет

Из чего состоит багзилла?

С внешней точки зрения

Крупные блоки функционала в Bugzilla с точки зрения пользователя:

  • Создание, просмотр, изменение багов, история по багу
    • Форматирование комментариев
  • Создание, просмотр, изменение вложений
  • Поиск багов, форма поиска
  • Графики одни, графики другие, Summarize Time
  • Исходящая почта
  • Входящая почта
  • Запросы флагов
  • Напоминания (whining)
  • Регистрация, изменение, раздача групп пользователям
  • Пользовательские настройки
  • XML-импорт (в полудохлом виде)
  • Web-сервисы (XML-RPC, JSON-RPC)
  • Sanity Check
  • Графы и деревья зависимостей
  • Голосование за баги

Наши крупные фичи

  • SCRUM-карточки
  • RSS-лента активности
  • XML-Simple Web-сервисы
  • Проверки корректности
  • Excel-импорт
  • Информер
  • Глобальная авторизация
  • Today Worktime, Super Worktime

Плюс вагон и маленькая тележка мелочи.

Страница создания бага (enter_bug.cgi)

Текущая логика страницы создания бага:

  • выбор classification, если она включена
  • classification выбрана → выбор product, если он не вообще один
  • тот же выбор продукта/классификации при клонировании багов
  • продукт выбран → форма создания бага
  • показ корректных списков возможных значений полей:
    • типы флагов в зависимости от компонента
    • cf_agreement в зависимости от продукта
    • списки пользователей, относящихся к багу в combo-box’ы
    • опциональный запрет на ввод приоритета на основе конфигурации (letsubmitterchoosepriority)
    • список флажков — ограничителей доступа группами
  • значения полей по умолчанию:
    • которые совсем по умолчанию
    • угадывание op_sys и rep_platform на основе заголовков запроса
    • версия, qa_contact, assigned_to, cc по умолчанию для компонента
    • хитрая логика для изменения списков cc при выборе компонентов
    • assigned_to=ты при выборе статуса ASSIGNED
    • показ поля resolution при выборе закрытого статуса
    • версия из cookies
    • загруженные из шаблона ввода бага
    • загруженные из клонированного бага
      • ссылка на старый аттач в описании нового клонированного бага
      • хитрая логика для CC при клонировании багов
  • напоминания о вводе времени
  • предпросмотр комментариев
  • добавление ссылки на старый аттач для клонированного из коммента бага
  • переключатель Show Expert Fields
  • Submit по Ctrl-Enter

Обработчик создания бага (post_bug.cgi)

  • Редирект на enter_bug.cgi если форма не заполнена
  • Проверка, не был ли уже использован этот token для постановки другого бага?
  • Вывод страницы с URL-шаблоном постановки бага, если попросили
  • Заполнение описания по шаблону, если использовался спецвид формы (например create_guided)
  • Отправка куки VERSION
  • Начало/конец транзакции в БД
  • Постановка собственно бага (Bug::create)
  • Постановка вложения сразу при создании бага
  • Постановка флагов на вложение
  • Постановка флагов на баг
  • Рассылка почты по багу, по его зависимостям
  • Вывод сообщения о том, что баг поставлен, о том, что CC-список обрезан по группе

Обработчик изменения бага (process_bug.cgi)

  • Начало/конец транзакции в БД
  • SELECT FOR UPDATE багов
  • Удаление значений полей, равных dontchange (используется в групповом редактировании)
  • Угадывание юзеров по части логина/имени
  • Проверка коллизии по delta_ts, вывод только действительно изменённых полей в форму подтверждения
  • Проверка token формы
  • Загрузка следующего бага из списка ДО обновления текущего (O_O)
  • Проверка прав редактирования по всем багам
  • Установка значения продукта до всех остальных полей
  • Установка новых групп до остальных изменений, когда включён strict_isolation
  • Установка/изменение флагов
  • Установка зависимостей бага
  • Установка ключевых слов
  • Установка остальных полей
  • Установка некоторых значений только для обновлений отдельных багов (alias, cclist_accessible, reporter_accessible, isprivate на комментах)
  • БОльшая часть логики обновления поля CC
  • Вызов функции проверки strict_isolation
  • Перемещение багов (MOVE) — полуживое, хз как работает
  • И после всего этого — ещё изменения, status, resolution, dup_id
  • Теперь всё это ещё незакоммичено, вызов реальных обновлений из Bug.pm
  • Отправка почты по зависимостям, если статус поменялся с открытого на закрытый
  • Обрезание CC-списка по группе (наше)
  • Сообщение, если очищено remaining_time
  • Удаление голосов за баг, если переместили в другой продукт, и проверка голосо-подтвеждённости бага
  • Отправка почты CC, Assignee, Reporter’у, QA, старым Assignee, QA и CC
  • Отправка почты по багу, дубликатом которого был помечен данный
  • Добавление нескольких вложений (наше)
  • Отправка почты по флагам, через отдельный механизм сбоку
  • Редирект (наше) или показ следующего/того же/никакого бага
  • Не показывает ничего, если USAGE_MODE_EMAIL

Список/поиск багов (buglist.cgi)

  • SuperWorkTime (наше доработко)
  • Редирект на форму поиска для пустого запроса
  • Редирект на форму поиска с добавленными пустыми полями, если на форме поиска жмут Add/Remove поле без JS
  • Редирект с POST’енного запроса на GET
  • Быстрый поиск
  • Посыл на хрен, если content пуст при включённом параметре specific_search_allow_empty_words
  • Обратная совместимость: format=rdf -> ctype=rdf, ctype=rss -> ctype=atom
  • ctype=js слать в хрен
  • Поддержка server-push
  • regetlastlist — открытие последнего просмотренного списка багов (берётся из куки)
  • Удаление колонки relevance из списка колонок, если юзер не просит полнотекстовый поиск
  • В buglist.cgi почему-то тусуются функции InsertNamedQuery, LookupSeries, GetQuip, GetGroups
  • Генерация имени файла, если попросили список не в html-формате
  • Выполнение запросов поиска
  • Сохранение и удаление сохранённых запросов из БД
  • Запуск series
  • Если сохранённый запрос не говорил нам свой формат, решаем что advanced
  • Вкуривание списка колонок (длинное, тварь)
  • Определение порядка сортировки
  • Подсчёт сумм значений полей таймтрекинга
  • Проверка доступа к багам и установка значения в implied или manual (х.з зачем нужно)
  • Массовое редактирование багов, в частности сборка пересечений доступных значений полей
  • Кодировка CSV (наше доработко)

Система прав

Какие в Bugzilla есть права / ограничения доступа?

  • Права доступа к продуктам
    • MANDATORY / SHOWN / DEFAULT / NA
    • Entry
    • Canedit
    • Editcomponents
    • Canconfirm
    • Editbugs
  • Private комментарии — видны только инсайдер-группе
  • Информация о таймтрекинге — видна только группе, списывающей время
  • reporter_accessible, cclist_accessible на отдельных багах
  • Опциональные группы на отдельных багах
  • Права на правку отдельных групп
  • Права editclassifications, editcomponents, editfields, editkeywords, editusers

Хорошо бы рефакторить

Что нужно рефакторить, чтобы багзилла перестала быть говном:

Внешности

  • Историю изменений бага показывать прямо внутри комментов (как в trac, кстати)
  • Добавить ленту обновлений по багам, поиск «последних багов», что-то типа форум-функциональности
  • Добавить грид
  • Необязательно — кучу действий, связанных с поиском, расположенных внизу, можно на вики-манер превратить во вкладочки
  • keywords переделать в «теги»
  • Заменить YUI на jQuery, форму поиска сгруппировать (но не так, как в 4.0), сделать почти все фильтры добавляемыми, добавляемыми через JS, чтобы не перезагружалась страница каждый раз
  • Механизм «ограничения показа»… кастом-полей переделать в древовидную структуру
  • Переделать систему прав:
    Сейчас продукт задаёт, по сути, КНФ («группа = юзеры + набор групп; продукт = & групп»), а задавать тупо доступ ОДНОЙ группой. А группа может быть либо пересечением N других, либо объединением N других + юзеров. Причина: пересечение в реальности используется ОЧЕНЬ редко.
    Соответственно все включения в группы сделать, чтобы материализовывались и чтобы вычислять каждый раз вычислять замыкания по наборам групп было не нужно. Это упростит и ускорит ВСЁ, в первую очередь фильтрацию.
  • Документацию в вики, POD-документацию по коду туда же

Кишки

  • Историю изменений по багам хранить в общей таблице с комментариями. История по другим объектам тоже хранить там же. То есть нужен такой вот общий метод хранения истории по всем сущностям.
  • Переписать код отправки почты, во-первых так чтобы отправлялась она через одно место, а не через 2 (в оригинале 3) разных, во-вторых чтобы параметры формировались не через жопу.
  • Добавить глобальный объект статуса, в который сохранять почту, которую нужно отправить, и сообщения, которые сейчас руками сохраняются в сессию.
  • Использовать не CGI, а хеши параметров запроса, это улучшает и производительность, и переносимость, и спасает от кучи глюков, так как например, $cgi->param может вернуть как список, так и скаляр
  • Использовать не кучу CGI-скриптов, а несколько (мало) точек входа и классы, всю мохнатую логику перенести в них. Не использовать модуль CGI.pm вообще (!), потому что он прости господи даже без use strict написан и порождает некоторые глюки сам по себе. В крайней форме точки доступа — это один index.cgi, позволяющий работать через CGI, один server.fcgi (FastCGI), один модуль для апача, и м.б. один для HTTP::Server::Simple. Ибо по сути, интерфейс «запроса» тривиален и не требует никакого бешеного CGI.
  • Прокачать объектное ядро, чтобы 90 % полей настраивались / отключались + см. начало статьи типа ORM.
  • Убрать ВСЕ строки (terms и т. п.) и описания ошибок из шаблонов и «длинного if’а», добавить отдельный уровень «локализации» / «таблицы строк», и переместить всё это в него. Потенциально также переместить туда же вообще все строки/тексты из шаблонов, для лёгкой локализации.
  • Обязательно оставить совместимость с PostgreSQL, но генерилка запросов не должна на это рассчитывать и должна работать оптимальнее, чем сейчас, например, в смысле дурацких подзапросов assigned_to=(select id from profiles where login_name=?) и т. п.
  • Желательно заменить Template::Toolkit на что-нибудь, хотя бы даже на моё поделие VMX::Template, чтобы не общаться с этим тормозом. Но как МИНИМУМ, даже если не заменять — убрать ВСЮ обратную связь с БД из шаблонов, ибо именно она убивает производительность в первую очередь! Ну ладно, не в первую, а во вторую, после дурацкого движка поиска.