Как-то не задавался таким вопросом, а тут поставил YiiDebugToolbar и удивился.
Memory Usage в районе 7 мегабайт.
Что влияет на использование памяти? Как уменьшить этот объем?
Как-то не задавался таким вопросом, а тут поставил YiiDebugToolbar и удивился.
Memory Usage в районе 7 мегабайт.
Что влияет на использование памяти? Как уменьшить этот объем?
На объем памяти очень сильно влияет активное использование ActiveRecord. Каждый объект при выборке из базы будет занимать место, а если их десятки…
Да, ActiveRecord используется активно.
Что делать?
Как вариант я бы предложил использовать кэширование
'db'=>array(
'class' => 'CDbConnection',
..
'schemaCachingDuration' => 86400,
}
schemaCachingDuration больше 0
или переписать запросы на чистом PDO
Вот из моего старого поста:
Обычный sql усложняет жизнь длинными запросами и переход с ActiveRecord на sql может быть еще здорово усложнен, если мы используем named-scope’ы.
Я не нашел возможности формировать чистый sql с помощью named-scope’ов и переписал метод findAll() для моделей:
public function findAll($condition = '', $params = array())
{
$criteria = $this->getDbCriteria();
if ($condition)
$criteria->mergeWith(array(
'condition' => $condition,
'params' => $params));
$this->_c=null;
return $this->getCommandBuilder()->createFindCommand($this->getTableSchema(), $criteria)->queryAll();
}
Теперь, например, $posts = Post::model()->findAll() возвращает в большом массиве все найденные записи, а не объекты этих записей. Это позволяет очень здорово экономить память.
Единственно, теперь к $posts надо обращаться не как к массиву объектов, а как к массиву массивов. Это требует простого исправления с $post->title на $post[‘title’], например.
Ну, и конечно можна не переписывать findAll(), а написать параллельно что-то свое, типа getAll(). И также можно поработать с остальными методами из "семейства" find.
А в случае использования древовидных структур я бы советовал запросы к БД в рекурсивных функциях заменить на один большой запрос с использованием INNER. Или можно изменить строение самой БД.
Интересное решение, а как быть если используется with:
$last = Articles::model()->with("users", "category")->findAll($criteria);
В таком варианте Articles::findAll не вызывается, ибо отрабатывает CActiveFinder.
Чего делать?
По поводу древовидных структур, хочется сделать комментарии в древовидном виде.
На пост вероятное распределение комментариев такое:
80% постов - до 10 комментариев, вложенность максимум (3-4)
15% постов - до 50 комментариев, вложенность до 8
5% постов - больше 50 комментариев, вложенность хз
Комментарии будут "смотреться" во много раз чаще, чем "писаться".
Какую структуру БД лучше использовать?
Перегрузить CActiveFinder::runQuery (этот метод превращает массивы в объекты), а также CActiveRecord::with, чтоб он использовал не CActiveFinder, а наш унаследованный класс.
А вообще по этой теме (массивы vs объекты) нужно отслеживать в исходниках фреймворка использование методов с именем populateRecord или populateRecords (эти методы есть в классах CActiveRecord и CJoinElement).
Для древовидных комментариев я знаю два неплохих метода:
1. Для этого нужны три поля:
order - varchar(255) (нужен индекс)
level - int(11)
parend - int(10) unsigned
Смысл в том, что order формируется необычным способом. Одновременно он указывает на порядок и глубину.
Вот, например есть дерево:
Первый комментарий - 00001
Второй - 00002
Комментарий - 0000200001
Комментарий - 000020000100001
Комментарий - 0000200002
Третий - 00003
Вот метод beforeSave() для модели использующей такой подход:
public function beforeSave()
{
if ($this->isNewRecord)
{
$sql = 'SELECT COUNT(*) FROM `' . Comment::tableName() . '` WHERE `task` = ' . $this->task . ' AND `parent` = ' . $this->parent;
$count = Yii::app()->db->createCommand($sql)->queryScalar();
if ($this->parent > 0)
{
$sql = 'SELECT `order` FROM `' . Comment::tableName() . '` WHERE `id` = ' . $this->parent;
$parent_order = Yii::app()->db->createCommand($sql)->queryScalar();
} else
$parent_order = '';
$this->order = $parent_order . str_pad($count+1, 5, '0', STR_PAD_LEFT);
$this->level = (strlen($this->order)/5)-1;
}
return parent::beforeSave();
}
А выборка вообще очень проста:
$sql = 'SELECT * FROM `' . Comment::tableName() . '` WHERE `task` = ' . $task->id . ' ORDER BY `order`';
$comments = Yii::app()->db->createCommand($sql)->queryAll();
Единственная как бы "проблема" метода - максимальная вложенность составляет 51 уровень и максимальное количество непосредственных наследников к одной записи - 99999. Но думаю, что этого с лихвой всем хватит.
2. Второй метод заключается в том, чтобы также одним запросом выбрать все комментарии, а потом в рекурсивной функции рассортировать все так, как нужно. Этот метод используется в LiveStreet, можете покопать там в файле classes/modules/comment/Comment.class.php методом LsComment::BuildCommentsRecursive.
Лично я предпочитаю первый метод. Он очень прост и эффективен. Хотя был бы очень рад услышать и критику, возможно есть какие-то подводные камни, а я про них не догадываюсь.
первый способ плох тем, что:
дико трахает базу
сложнее удалять, перемещять ветки
сам еще не сталкивался и не тестил, но во втором способе имхо можно попробовать закешировать сырые идексы и потом их заюзать, чтоб не разбирать заново дерево
О первом способе:
Нагружает базу размером? Или еще чем-то? Можно вместо varchar использовать char
Удалять просто - взял и удалил одним запросом. Даже не нужно трогать другие строки.
А перемещать и менять порядок - да, сложно. Но для комментов это обычно и не требуется.
О втором:
Не понял, что значит "закешировать сырые идексы". Объясните, пожалуйста.
дополнительные селекты на каждый инсерт
ну например удалили "Второй - 00002" и "Комментарий - 0000200001", остается
Комментарий - 000020000100001
уже непонятно какая вложенность получается и как интерпретировать результат
допустим, сначало рекурсивной ф-ей строяться "сырые индексы" по типу:
Array(
[1] => Array(
[2] => Array(
[4] => Array(
[6] => Array()
)
[7] => Array()
)
)
[3] => Array()
)
потом, по индексам строится дерево коментов, плюсом будет то, что такую струкруру индексов легко использовать
+в первом методе имхо труднее интерпритировать результат выборки в древрвидную струкруту в хтмле.
может канечно это не минус, но такие вещи выглядят не очень красиво: $this->order = $parent_order . str_pad($count+1, 5, ‘0’, STR_PAD_LEFT); $this->level = (strlen($this->order)/5)-1;, способ по индексам имхо более лаконичный.
другое дело, если использовать вместо 000020000100001 сериализованный массив или объект, на на это, боюсь, понадобится поле Text )
итого: каждому решать самостятельно, какой способ использовать, просто у первого способа я лично не вижу никаких преимуществ.
Получается, что "Комментарий - 000020000100001" остался без родителя. Можно его вывести сразу, как коммент третьего уровня без предков.
А если при удалении объекта нужно удалять всех его наследников, то можно писать например LIKE "00002%"
Да, согласен, каждому решать самостоятельно. Лишь бы не "в лоб", когда для каждого объекта наследники ищутся через отдельный запрос.
Вообще не существует универсального способа хранить и работать в реляционной баз с древовидными структурами.
Всегд приходится чем то жертвовать
либо скоростью либо удобством.
Для работы с деревом вероятно самый правильный способ это вынос всех операций на уровень БД (хранимые процедуры).
Но опять-таки всегда приходится искать компромисс, далеко не всегда нужны все 99999 эоементов с 51м уровнемвложенности,
все зависит от требований к пишущемуся ПО.
Сделал пока ID-ParentID.
Выбираю все комменты, у который UID равен заданному одним запросом и далее в скрипте распихиваю их по дереву.
Кстати, еще не работал с кешированием.
Ведь можно как-то прикрутить тут кеширование, что бы комменты хранились уже отредеренные какое-то время и только при апдейте или добавлении нового коммента кеш обновлялся.
В какую сторону смотреть?
Да, ты прав. Поэтому я уже некоторое время смотрю в сторону документо-ориентированных баз. Например, мне очень понравился MongoDB. Думаю в одном следующем проекте его активно использовать. Правда для этого виртуальный хостинг не прокатит - нужен VDS/VPS
Смотрите в сторону постгреса. Там есть встроенные деревья работа с которыми организована через хранимые процедуры. В итоге все работает очень шустро.