[SPIP] v3.2.11 -> v3.2.12
[lhc/web/www.git] / www / ecrire / genie / optimiser.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Gestion des optimisations de la base de données en cron
15 *
16 * @package SPIP\Core\Genie\Optimiser
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 include_spip('base/abstract_sql');
24 include_spip('inc/config');
25
26 /**
27 * Cron d'optimisation de la base de données
28 *
29 * Tache appelée régulièrement
30 *
31 * @param int $t
32 * Timestamp de la date de dernier appel de la tâche
33 * @return int
34 * Timestamp de la date du prochain appel de la tâche
35 **/
36 function genie_optimiser_dist($t) {
37
38 optimiser_base_une_table();
39 optimiser_base();
40 optimiser_caches_contextes();
41
42 // la date souhaitee pour le tour suivant = apres-demain a 4h du mat ;
43 // sachant qu'on a un delai de 48h, on renvoie aujourd'hui a 4h du mat
44 // avec une periode de flou entre 2h et 6h pour ne pas saturer un hebergeur
45 // qui aurait beaucoup de sites SPIP
46 return -(mktime(2, 0, 0) + rand(0, 3600 * 4));
47 }
48
49 /**
50 * Vider les contextes ajax de plus de 48h
51 */
52 function optimiser_caches_contextes() {
53 sous_repertoire(_DIR_CACHE, 'contextes');
54 if (is_dir( $d = _DIR_CACHE . 'contextes')) {
55 include_spip('inc/invalideur');
56 purger_repertoire($d, ['mtime' => time() - 48*24*3600, 'limit' => 10000]);
57 }
58 }
59
60 /**
61 * Optimise la base de données
62 *
63 * Supprime les relicats d'éléments qui ont disparu
64 *
65 * @note
66 * Heure de référence pour le garbage collector = 24h auparavant
67 * @param int $attente
68 * Attente entre 2 exécutions de la tache en secondes
69 * @return void
70 **/
71 function optimiser_base($attente = 86400) {
72 optimiser_base_disparus($attente);
73 }
74
75
76 /**
77 * Lance une requête d'optimisation sur une des tables SQL de la
78 * base de données.
79 *
80 * À chaque appel, une nouvelle table est optimisée (la suivante dans la
81 * liste par rapport à la dernière fois).
82 *
83 * @see sql_optimize()
84 *
85 * @global int $GLOBALS ['meta']['optimiser_table']
86 **/
87 function optimiser_base_une_table() {
88
89 $tables = array();
90 $result = sql_showbase();
91
92 // on n'optimise qu'une seule table a chaque fois,
93 // pour ne pas vautrer le systeme
94 // lire http://dev.mysql.com/doc/refman/5.0/fr/optimize-table.html
95 while ($row = sql_fetch($result)) {
96 $tables[] = array_shift($row);
97 }
98
99 spip_log("optimiser_base_une_table ".json_encode($tables), 'genie'._LOG_DEBUG);
100 if ($tables) {
101 $table_op = intval(lire_config('optimiser_table', 0) + 1) % sizeof($tables);
102 ecrire_config('optimiser_table', $table_op);
103 $q = $tables[$table_op];
104 spip_log("optimiser_base_une_table : debut d'optimisation de la table $q", 'genie'._LOG_DEBUG);
105 if (sql_optimize($q)) {
106 spip_log("optimiser_base_une_table : fin d'optimisation de la table $q", 'genie'._LOG_DEBUG);
107 } else {
108 spip_log("optimiser_base_une_table : Pas d'optimiseur necessaire", 'genie'._LOG_DEBUG);
109 }
110 }
111 }
112
113
114 /**
115 * Supprime des enregistrements d'une table SQL dont les ids à supprimer
116 * se trouvent dans les résultats de ressource SQL transmise, sous la colonne 'id'
117 *
118 * @note
119 * Mysql < 4.0 refuse les requetes DELETE multi table
120 * et elles ont une syntaxe differente entre 4.0 et 4.1
121 * On passe donc par un SELECT puis DELETE avec IN
122 *
123 * @param string $table
124 * Nom de la table SQL, exemple : spip_articles
125 * @param string $id
126 * Nom de la clé primaire de la table, exemple : id_article
127 * @param Resource $sel
128 * Ressource SQL issue d'une sélection (sql_select) et contenant une
129 * colonne 'id' ayant l'identifiant de la clé primaire à supprimer
130 * @param string $and
131 * Condition AND à appliquer en plus sur la requête de suppression
132 * @return int
133 * Nombre de suppressions
134 **/
135 function optimiser_sansref($table, $id, $sel, $and = '') {
136 $in = array();
137 while ($row = sql_fetch($sel)) {
138 $in[$row['id']] = true;
139 }
140 sql_free($sel);
141
142 if ($in) {
143 sql_delete($table, sql_in($id, array_keys($in)) . ($and ? " AND $and" : ''));
144 spip_log("optimiser_sansref: Numeros des entrees $id supprimees dans la table $table: " . implode(', ', array_keys($in)), 'genie'._LOG_DEBUG);
145 }
146
147 return count($in);
148 }
149
150
151 /**
152 * Suppression des liens morts entre tables
153 *
154 * Supprime des liens morts entre tables suite à la suppression d'articles,
155 * d'auteurs, etc...
156 *
157 * @note
158 * Maintenant que MySQL 5 a des Cascades on pourrait faire autrement
159 * mais on garde la compatibilité avec les versions précédentes.
160 *
161 * @pipeline_appel optimiser_base_disparus
162 *
163 * @param int $attente
164 * Attente entre 2 exécutions de la tache en secondes
165 * @return void
166 **/
167 function optimiser_base_disparus($attente = 86400) {
168
169 # format = 20060610110141, si on veut forcer une optimisation tout de suite
170 $mydate = date("Y-m-d H:i:s", time() - $attente);
171 $mydate_quote = sql_quote($mydate);
172
173 $n = 0;
174
175 //
176 // Rubriques
177 //
178
179 # les articles qui sont dans une id_rubrique inexistante
180 # attention on controle id_rubrique>0 pour ne pas tuer les articles
181 # specialement affectes a une rubrique non-existante (plugin,
182 # cf. https://core.spip.net/issues/1549 )
183 $res = sql_select("A.id_article AS id",
184 "spip_articles AS A
185 LEFT JOIN spip_rubriques AS R
186 ON A.id_rubrique=R.id_rubrique",
187 "A.id_rubrique > 0
188 AND R.id_rubrique IS NULL
189 AND A.maj < $mydate_quote");
190
191 $n += optimiser_sansref('spip_articles', 'id_article', $res);
192
193 // les articles a la poubelle
194 sql_delete("spip_articles", "statut='poubelle' AND maj < $mydate_quote");
195
196 //
197 // Auteurs
198 //
199
200 include_spip('action/editer_liens');
201 // optimiser les liens de tous les auteurs vers des objets effaces
202 // et depuis des auteurs effaces
203 $n += objet_optimiser_liens(array('auteur' => '*'), '*');
204
205 # effacer les auteurs poubelle qui ne sont lies a rien
206 $res = sql_select("A.id_auteur AS id",
207 "spip_auteurs AS A
208 LEFT JOIN spip_auteurs_liens AS L
209 ON L.id_auteur=A.id_auteur",
210 "L.id_auteur IS NULL
211 AND A.statut='5poubelle' AND A.maj < $mydate_quote");
212
213 $n += optimiser_sansref('spip_auteurs', 'id_auteur', $res);
214
215 # supprimer les auteurs 'nouveau' qui n'ont jamais donne suite
216 # au mail de confirmation (45 jours pour repondre, ca devrait suffire)
217 sql_delete("spip_auteurs", "statut='nouveau' AND maj < " . sql_quote(date('Y-m-d', time() - 45 * 24 * 3600)));
218
219 /**
220 * Permet aux plugins de compléter l'optimisation suite aux éléments disparus
221 *
222 * L'index 'data' est un entier indiquant le nombre d'optimisations
223 * qui ont été réalisées (par exemple le nombre de suppressions faites)
224 * et qui doit être incrémenté par les fonctions
225 * utilisant ce pipeline si elles suppriment des éléments.
226 *
227 * @pipeline_appel optimiser_base_disparus
228 */
229 $n = pipeline('optimiser_base_disparus', array(
230 'args' => array(
231 'attente' => $attente,
232 'date' => $mydate
233 ),
234 'data' => $n
235 ));
236
237
238 spip_log("optimiser_base_disparus : {$n} lien(s) mort(s)", 'genie'._LOG_DEBUG);
239 }