getMemoryUsage

Как-то не задавался таким вопросом, а тут поставил 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.

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

первый способ плох тем, что:

  1. дико трахает базу

  2. сложнее удалять, перемещять ветки

сам еще не сталкивался и не тестил, но во втором способе имхо можно попробовать закешировать сырые идексы и потом их заюзать, чтоб не разбирать заново дерево

О первом способе:

  1. Нагружает базу размером? Или еще чем-то? Можно вместо varchar использовать char

  2. Удалять просто - взял и удалил одним запросом. Даже не нужно трогать другие строки.

А перемещать и менять порядок - да, сложно. Но для комментов это обычно и не требуется.

О втором:

Не понял, что значит "закешировать сырые идексы". Объясните, пожалуйста.

дополнительные селекты на каждый инсерт

ну например удалили "Второй - 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

Смотрите в сторону постгреса. Там есть встроенные деревья работа с которыми организована через хранимые процедуры. В итоге все работает очень шустро.