Удаляются ли временные данные (transients) автоматически?

9 янв. 2011 г., 02:20:17
Просмотры: 35K
Голосов: 69

Этот вопрос возник после прочтения Временные RSS-ленты в wp_options не удаляются автоматически?

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

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

Значит ли это, что они навсегда останутся в базе данных?

25
Комментарии

теоретически они должны удаляться при выполнении cron (если их срок действия истек)

onetrickpony onetrickpony
9 янв. 2011 г. 02:34:01

@Ambitious Amoeba Я не вижу ничего, что было бы подключено к cron с такой функциональностью. Поэтому я спрашиваю - похоже, это предположение, в правильности которого я не уверен.

Rarst Rarst
9 янв. 2011 г. 02:37:59

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

t31os t31os
9 янв. 2011 г. 18:35:47

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

Rarst Rarst
9 янв. 2011 г. 18:38:59

@Rarst - Я тоже не уверен, как определяется очистка, вы наблюдаете эту проблему с какими-то конкретными временными данными или разными?

t31os t31os
9 янв. 2011 г. 18:46:09

@t31os Я не собираюсь тратить время на создание системы логирования временных данных, пока не узнаю, должны ли они вообще удаляться сборщиком мусора. :)

Rarst Rarst
9 янв. 2011 г. 18:58:46

@Rarst - Чёрт его знает, приятель, просто хотел поделиться парой мыслей.. :)

t31os t31os
9 янв. 2011 г. 19:11:06

похоже, что просроченные временные данные удаляются при срабатывании get_transient - http://core.trac.wordpress.org/browser/tags/3.0.4/wp-includes/functions.php#L721

onetrickpony onetrickpony
9 янв. 2011 г. 19:11:44

Так что в базе данных не должно быть просроченных временных данных, разве что если delete_option() сработал неправильно

onetrickpony onetrickpony
9 янв. 2011 г. 19:13:18

@Ambitious Amoeba да, я вроде как упомянул это в вопросе. Моя мысль в том, что создание транзиента не предполагает и не гарантирует, что он когда-либо будет запрошен. Подчеркиваю исходный вопрос - когда и если просроченный транзиент удаляется, если я никогда его не получаю?

Rarst Rarst
9 янв. 2011 г. 19:17:47

но в чем тогда смысл использования транзиентов?

onetrickpony onetrickpony
9 янв. 2011 г. 19:19:58

@Ambitious Amoeba смысл в том, что транзиенты - это механизм кэширования. Концепция кэша предполагает устаревание данных и не гарантирует обязательных попаданий. Если кэш не очищает устаревшие данные, то это утечка ресурсов.

Rarst Rarst
9 янв. 2011 г. 19:29:23

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

onetrickpony onetrickpony
9 янв. 2011 г. 19:36:11

@Rarst - Звучит как идеальная задача для написания патча и отправки в трек?

MikeSchinkel MikeSchinkel
9 янв. 2011 г. 20:46:39

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

Rarst Rarst
9 янв. 2011 г. 21:01:55

@Rarst - Единственный способ точно узнать — это пройтись по коду...

MikeSchinkel MikeSchinkel
10 янв. 2011 г. 05:29:29

Их не нужно "собирать как мусор". Если вы никогда их не запрашиваете, то неважно, есть они там или нет.

Otto Otto
12 сент. 2011 г. 05:51:55

@Otto если у вас начинают скапливаться десятки тысяч таких мусорных записей в таблице options (что действительно случается на практике, см. прикреплённый вопрос), я думаю, это довольно важно, не так ли?

Rarst Rarst
12 сент. 2011 г. 11:12:09

Нет, на самом деле это не так. Базы данных могут содержать миллионы и миллионы строк без заметного замедления. Это называется "индексированием", и база остаётся очень быстрой даже с огромным количеством строк: http://en.wikipedia.org/wiki/Index_(database).

Более того, вызов SQL DELETE не удаляет строки из базы данных на самом деле. Он просто убирает их из индекса, пока вы не выполните OPTIMIZE TABLE для этой таблицы, что является долгой операцией. Обычно нет необходимости "чистить" записи в базе данных. Доверьтесь базе данных — она справляется с этим лучше, чем вы.

Otto Otto
12 сент. 2011 г. 12:11:48

Если говорить более конкретно, вы могли заметить, что транзиенты в базе данных имеют флаг autoload, установленный в "no", что означает, что они не загружаются при запуске. Основное замедление в любом запросе к базе данных происходит из-за передачи данных из базы в программу. Сами запросы, если они написаны правильно и должным образом проиндексированы (то есть запрос не вызывает полного сканирования таблицы), занимают практически ничтожное время по сравнению с этим. Неважно, есть у вас 100 записей или 100 000 — простой SELECT по индексированному полю является операцией O(log(n)). Это не меняется вплоть до 1M+ записей.

Otto Otto
12 сент. 2011 г. 12:26:28

@Otto Не могли бы вы перенести это в ответ, чтобы информация была более заметной? Я не спорю, что нужно очень много мусорных записей, чтобы всё испортить... Но если что-то утекает (а с транзиентами это легко происходит, потому что у них есть ограничение на длину ключа, которое не проверяется, и можно легко создать кучу транзиентов, которые больше никогда не будут использованы из-за сломанного ключа), то рано или поздно это всё испортит. Я не кричу "исправьте это в ядре", но и не считаю очистку бесполезной.

Rarst Rarst
12 сент. 2011 г. 12:36:39

"Накосячить" — довольно расплывчатое утверждение. Максимум, что может произойти — это превышение лимита места в базе данных для ограниченного аккаунта. Ничего не сломается, пока база не станет действительно очень-очень большой. Я работаю с таблицами, содержащими 20 миллионов записей. Поиск по ним немного медленный, но не чрезмерно. Вы правы насчёт ограничения длины ключа, но 45 символов более чем достаточно для любого реалистичного случая, который я могу представить. Да, конечно, можно сделать что-то безумное, но часто ли это происходит? Кажется, лучше уведомить автора плагина, чем искать обходные пути...

Otto Otto
12 сент. 2011 г. 20:54:56

@Otto "не часто" и "никогда" — разные вещи. Если я доведу этот код до состояния плагина, я планирую добавить предупреждение о длине ключа. Однако я не вижу смысла хранить мусорные данные в базе только потому, что временные записи работают именно так. Возможно, это и не баг, но вряд ли это можно назвать фичей.

Rarst Rarst
12 сент. 2011 г. 21:12:59

Моя мысль не в том, чтобы хранить их в базе только потому, что они так работают. Я говорю, что попытки "сборки мусора" обходятся дороже, чем экономят, практически во всех случаях. Это контрпродуктивно.

Otto Otto
12 сент. 2011 г. 21:33:04

Связанный тикет в Trac: http://core.trac.wordpress.org/ticket/20316

Stephen Harris Stephen Harris
30 сент. 2013 г. 00:19:32
Показать остальные 20 комментариев
Все ответы на вопрос 3
11
53

Теперь это так

Начиная с WordPress 3.7, просроченные транзиенты удаляются при обновлении базы данных, см. #20316


Старый ответ

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

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

add_action( 'wp_scheduled_delete', 'delete_expired_db_transients' );

function delete_expired_db_transients() {

    global $wpdb, $_wp_using_ext_object_cache;

    if( $_wp_using_ext_object_cache )
        return;

    $time = isset ( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time() ;
    $expired = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout%' AND option_value < {$time};" );

    foreach( $expired as $transient ) {

        $key = str_replace('_transient_timeout_', '', $transient);
        delete_transient($key);
    }
}
10 янв. 2011 г. 11:55:47
Комментарии

$time = $_SERVER['REQUEST_TIME']; и затем использование $time в SQL-запросе - не делайте так. Будьте более осторожны с переменными/значениями $_SERVER, чтобы предотвратить SQL-инъекции.

hakre hakre
10 янв. 2011 г. 14:01:55

@hakre хм... Я взял это из презентации по производительности PHP, где рекомендовали использовать это вместо time(), которое может вызывать ошибки (выполнение не является мгновенным по своей природе). Время запроса устанавливается самим PHP, оно не поступает из каких-либо пользовательских данных. Почему это считается уязвимостью?

Rarst Rarst
10 янв. 2011 г. 14:08:39

@Rarst: Я не сказал, что не следует использовать это, просто нужно убедиться, что оно безопасно экранировано для использования в SQL-запросе. Так следует поступать с каждой переменной из внешнего источника. Переменные $_SERVER могут быть установлены не так, как ожидается, а вместо этого их может задать сам запрашивающий пользователь. Я всего лишь хотел продвинуть хорошие практики программирования. Как всегда, чтобы узнать реальное состояние доступности, обратитесь к документации. Например, для PHP 4 такой переменной не существует, и она может быть перезаписана пользовательским заголовком или переменной окружения - http://php.net/manual/en/reserved.variables.server.php

hakre hakre
10 янв. 2011 г. 14:12:14

@hakre исправлено (я думаю), кстати, спасибо за напоминание про PHP4 (я не могу дождаться, когда WordPress перестанет его поддерживать)

Rarst Rarst
10 янв. 2011 г. 14:19:14

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

hakre hakre
10 янв. 2011 г. 14:26:37

На случай, если ты не знал, _ — это wildcard-символ для одного символа в операторах LIKE, и его желательно экранировать. :)

Denis de Bernardy Denis de Bernardy
10 янв. 2011 г. 15:42:05

@Denis да, я это знаю... Но в этом запросе нет практической разницы?.. Разве что кто-то умудрится назвать опцию XtransientXtimeoutX или что-то в этом роде.

Rarst Rarst
10 янв. 2011 г. 16:35:31

Разве вы не должны использовать $wpdb->prepare(), чтобы должным образом защитить себя от некорректных данных, о которых говорил @hakre? Это также решит проблему экранирования символа '_'. Я бы рекомендовал использовать этот метод как лучшую практику.

Tom Auger Tom Auger
22 июн. 2012 г. 04:31:21

@Tom кроме как "чтобы быть абсолютно уверенным", этот конкретный запрос не требует prepare, и я не стал его добавлять.

Rarst Rarst
22 июн. 2012 г. 11:35:16

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

Tom Auger Tom Auger
22 июн. 2012 г. 20:24:37

Разве не должно быть _transient_timeout_%? Просто чтобы убедиться, что это действительно префикс, который использует WP? ;)

kaiser kaiser
28 июл. 2013 г. 18:04:54
Показать остальные 6 комментариев
9
21

Переношу некоторые комментарии из обсуждения в ответ, с перефразировкой и изменением форматирования.

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

Дело в том, что по умолчанию транзиенты хранятся в таблице options. В базовой установке таблица options может содержать около 100 записей. Каждый транзиент добавляет ещё две записи, но даже если их тысячи, это не влияет на скорость сайта, так как они не загружаются автоматически.

При запуске WordPress загружает опции в память, но только те, у которых включён флаг autoload. У транзиентов его нет, поэтому они не загружаются в память. Только транзиенты, которые действительно используются позже, несут затраты.

С точки зрения базы данных, таблица options имеет индексы как по option Id, так и по option name. Транзиенты всегда загружаются по имени (ключу), поэтому их поиск — это простые SELECT-запросы по уникальному ключу. Таким образом, поиск выполняется за O(log(n)) и очень быстр. С Big-O log(n) вам потребуются миллионы строк, чтобы это стало заметно. Честно говоря, накладные расходы на подготовку и завершение запроса, а также передачу данных занимают гораздо больше времени. Сам запрос выполняется практически мгновенно. Поэтому просто наличие неиспользуемых строк не влияет ни на что, кроме занимаемого места на диске.

Индексирование в базах данных — одна из тех сложных концепций, которые непонятны тем, кто не разобрался в происходящем "за кулисами". Базы данных изначально созданы для быстрого поиска данных и легко справляются с такими вещами. Вот полезная статья: http://en.wikipedia.org/wiki/Index_(database) (на английском).

Теперь о чистке. Самый очевидный способ (вызов SQL DELETE) на самом деле не удаляет записи из базы. Он просто убирает их из индекса и помечает строку как "удалённую". Так работают базы данных. Чтобы освободить место на диске, нужно выполнить OPTIMIZE TABLE, и это небыстрая операция. Она занимает время, вероятно, большее, чем стоит. В целом, это вряд ли сэкономит вам CPU-время.

Если у вас есть случай, когда постоянно создаются новые неиспользуемые транзиенты, нужно искать коренную проблему. Что их создаёт? Используются ли изменяющиеся ключи? Если да, то плагин или код, вызывающий это, следует исправить, чтобы он так не делал. Это будет полезнее, потому что код, который неправильно их создаёт, скорее всего, также не извлекает их, выполняя лишнюю работу.

С другой стороны, может быть случай, когда транзиенты создаются, например, для каждой записи. Это может быть совершенно нормально. Я сам так делаю в SFC для хранения входящих комментариев из Facebook. Каждая запись может иметь связанный транзиент, то есть две дополнительные строки на запись. Если у вас 10 тыс. записей, в таблице options будет 20 тыс. строк (в конечном итоге). Это не плохо и не медленно, потому что, опять же, разница между 100 и 20 000 строками для базы данных несущественна. Всё проиндексировано. Всё молниеносно. Доли миллисекунд.

Когда строк становится миллионы, тогда стоит забеспокоиться. Когда размер таблицы options превышает сотни мегабайт, стоит присмотреться внимательнее. Но в целом это не проблема, за исключением экстремальных случаев. Это точно не проблема для чего-то меньшего, чем крупный новостной сайт с сотнями тысяч записей. А для любого достаточно большого сайта, где это может стать проблемой, следует использовать внешнее кеширование объектов, и в таком случае транзиенты автоматически сохраняются там, а не в базе данных.

12 сент. 2011 г. 21:09:10
Комментарии

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

webaware webaware
29 июл. 2013 г. 03:48:50

Нет никаких причин использовать "временные данные без срока действия", потому что они по сути идентичны обычным "настройкам" (options).

Otto Otto
30 июл. 2013 г. 02:43:05

Конечно, но это значение по умолчанию. В результате многие авторы плагинов добавляют временные данные без срока действия.

webaware webaware
30 июл. 2013 г. 03:19:35

Также, этот вариант не идентичен настройке, так как он будет очищаться при использовании кэша объектов — подробности смотрите в недавней статье на WPEngine.

webaware webaware
30 июл. 2013 г. 03:21:32

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

Otto Otto
30 июл. 2013 г. 19:12:57

:) (потому что это не потому, что стандартное поведение WordPress ошибочно, да?)

webaware webaware
31 июл. 2013 г. 03:19:50

Это зависит. Какое значение вы считаете разумным по умолчанию?

Otto Otto
31 июл. 2013 г. 17:47:52

Например, 7 дней. Если автор плагина/темы хочет большее или меньшее значение, он его укажет. Если они хотят автозагрузку, им не нужно указывать 0 для срока действия (= бесконечность), но именно это они сейчас получают, когда параметр срока действия выполняет двойную роль параметра автозагрузки да/нет. В любом случае, срок действия по умолчанию не должен также подразумевать автозагрузку=да по умолчанию; это просто напрашивается на проблемы.

webaware webaware
1 авг. 2013 г. 04:02:58

По моему мнению, неуказание срока действия должно вызывать фатальную ошибку и ломать сайт. Но я не главный. Транзиент без срока действия — это глупо и бессмысленно. Если вы хотите использовать объектный кеш, то используйте его напрямую с помощью функций wp_cache. Тем не менее, есть тикеты, чтобы будущие версии WordPress очищали старые транзиенты, в основном потому что это "некрасиво", а не по какой-то другой причине.

Otto Otto
19 мар. 2014 г. 07:20:35
Показать остальные 4 комментариев
2
20

Отто - Я совершенно с тобой не согласен. Проблема в том, что со временем, со всеми этими транзиентами, размер таблицы становится абсурдно большим. Не нужно миллионов строк, чтобы система начала тормозить. Я сейчас работаю с таблицей опций, в которой более 130 тысяч строк, и она регулярно зависает. Поскольку поле value имеет тип large text, даже поиск только строк с "autoload" превращается в кошмар с точки зрения производительности. Эти поля value хранятся отдельно от остальных данных строки. Даже логически это часть одной таблицы, для выборки нужных строк должны происходить соединения (joins). Соединения, которые теперь занимают вечность, потому что нужные данные разбросаны по всему диску. Профилирование (с использованием Jet Profiler для MySQL) подтвердило это.

Добавление auto-load в кластерный ключ может помочь решить эту проблему. Кластеризация по Autoload Desc, ID ASC, например, позволит всем строкам с autoload группироваться вместе на диске. Но даже в этом случае, с точки зрения базы данных, нагрузка будет огромной.

Лично я считаю, что дизайн этой системы не продуман. Таблица опций превратилась в общую свалку для множества вещей. Это нормально, если поле value достаточно мало, чтобы включаться на ту же страницу, что и остальные данные строки, и может быть эффективно проиндексировано. К сожалению, это не так. Тот, кто это проектировал, должен вернуться на курсы по основам баз данных.

2 дек. 2011 г. 21:43:05
Комментарии

верно, но учтите, что когда начиналась разработка WordPress, никто не предполагал, что у него будут тысячи плагинов, использующих таблицу options как хранилище данных :)

onetrickpony onetrickpony
2 дек. 2011 г. 22:39:26

@onetrickpony именно поэтому важно всегда не торопиться и делать всё правильно, независимо от того, ожидаете ли вы, что проект когда-нибудь станет большим или нет :)

Mahmoud Al-Qudsi Mahmoud Al-Qudsi
12 дек. 2017 г. 01:16:23