Post meta vs tabele separate în baza de date
Când dezvolți plugin-uri care necesită stocarea datelor, care sunt avantajele și dezavantajele utilizării unei metode sau alteia?
Explicația oferită în codex nu este detaliată:
Înainte de a te grăbi să creezi un tabel nou, totuși, ia în considerare dacă stocarea datelor plugin-ului tău în Post Meta WordPress (cunoscut și ca Custom Fields) ar funcționa. Post Meta este metoda preferată; folosește-o când este posibil/practic.

Ei bine, dacă port pălăria unui WP script kiddie, răspunsul meu ar fi: folosește post_meta, mereu.
Totuși, se întâmplă să știu câteva lucruri despre baze de date, așa că răspunsul meu este: niciodată, dar niciodată, să folosești un EAV (adică tabelul post_meta) pentru a stoca date pe care s-ar putea să vrei să le interoghezi.
În ceea ce privește indexarea, practic nu există niciunul util în tabelele meta. Deci, dacă stochezi date de tip XYZ și speri să interoghezi toate postările care au XYZ cu valoarea 'abc'
, ei bine... noroc. (Vezi toate ticketele legate de utilizatori/roluri/capacități din WP trac pentru a-ți face o idee despre cât de urât poate deveni.)
În ceea ce privește join-urile, ajungi rapid la limita la care optimizerul decide să folosească un algoritm generic în loc să analizeze interogarea atunci când există mai multe criterii de join.
Așadar, nu, nu, nu, nu. Niciodată, dar niciodată, să folosești meta. Decât dacă ceea ce stochezi este cosmetic și nu va face niciodată parte dintr-un criteriu de interogare.
Totul se rezumă la aplicația ta. Dacă stochezi, să zicem, data nașterii unui regizor de film, atunci nu e mare lucru. Folosește meta cât vrei. Dar dacă stochezi, să zicem, data lansării unui film, ai fi nebun să nu folosești un tabel separat (sau să adaugi coloane în tabelul posts) și să adaugi un index pe acea coloană.

Da, plugin-urile pe care le dezvolt gestionează date personalizate precum evenimente, știri, comunicate de presă, oferte de joburi... Din afara "Universului WordPress", utilizarea tabelelor nu este cu adevărat o opțiune. Dar sfatul din WordPress Codex este puțin confuz. Cum pot fi preferate bucăți de date serializate în locul datelor normalizate/structurate/indexate?

Dacă îl întrebi pe un dezvoltator WP obișnuit, probabil va răspunde "folosește meta" sau "folosește o taxonomie". Și sunt de acord, până în punctul în care trebuie să interoghezi aceste date. Dacă da, și cred că este cazul tău, singurul meu răspuns este, adaugă câmpurile în tabelul posts sau creează un tabel separat. Altfel vei avea probleme enorme de performanță când vine vorba de interogări și, chiar mai important pentru liste de noduri, sortare top-n.

Denis, ai putea să dezvolți puțin mai mult acest subiect, îl găsesc foarte informativ dar aș dori niște date suplimentare, a făcut cineva teste?, care sunt exact principalele dezavantaje și limitări, mulțumesc.

@Denis - O apărare pasionată împotriva postmeta, nu-i așa? Știi că te îndepărtezi ferm de ortodoxie și că vei cădea din grațiile preoților înalți ai bisericii poeziei de cod dacă continui cu astfel de vorbe, nu-i așa? :-) Dar serios, nu crezi că exagerezi puțin? Depinde într-adevăr dacă vor fi zeci de mii de înregistrări meta sau nu. În multe cazuri, pur și simplu nu sunt suficiente înregistrări pentru a fi o problemă. Un site complex pe care îl implementez are în jur de 10.000 de înregistrări meta, cu puține înregistrări noi planificate, și funcționează bine (pentru informare, nu este un blog).

@Mike: 10k rânduri nu sunt multe. Când testez schema pe care o folosesc, o umplu cu 200k rânduri și mă asigur că niciuna dintre interogările comune nu rulează mai mult de câteva milisecunde. Asta include și cele legate de (graful orientat al) permisiunilor, aș adăuga. Voi face referire la un thread de SO pentru perspective suplimentare despre avantaje și dezavantaje: http://stackoverflow.com/questions/870808/entity-attribute-value-database-vs-strict-relational-model-ecommerce-question

@Mike: de asemenea, nu mă înțelege greșit. Folosesc EAV din când în când. Doar că mă limitez la stocarea de date neesențiale în el, cu indexuri parțiale pe tabela meta pentru câmpul ocazional care chiar are nevoie de unul, dar care clar nu aparține în tabela nod (un token de unică folosință sau o preferință booleană îmi vin în minte).

@Denis - Mulțumesc pentru comentarii. Și să nu mă înțelegi greșit, probabil sunt mult mai aproape de perspectiva ta, dar combinația dintre 1.) o dezbatere de o oră cu Matt la WordCamp Birmingham despre avantajele câmpurilor de tip Pods și 2.) simplitatea meta mă face să mă resemnez și să-mi concentrez atenția asupra altor probleme pe care aș putea să le schimb. La WCB am realizat că atâta timp cât Matt este la conducere, lucrurile nu se vor schimba pentru că (cred eu) Matt este atât de îndrăgostit de ideea de a avea mai puține tabele încât nu-și va permite să recunoască dezavantajele indexării pe o cheie de 768 de biți. <sigh>

Hehe. Cred că o să adori CMS-ul pe care îl dezvolt, dacă voi ajunge să-l lansez parțial sau integral. :-)

Dacă plugin-ul tău va gestiona O CANTITATE MARE de date, atunci utilizarea tabelei wp_postmeta
NU este o idee bună, așa cum este demonstrat mai jos:
Luând WooCommerce ca exemplu, într-un magazin cu ~30.000 de produse, va exista în medie ~40 de metadate (atribute și altele) per produs, 5 imagini per produs, ceea ce înseamnă că vor exista ~4 metadate pentru fiecare imagine:
30.000 produse x 40 metadate fiecare = 1.200.000 de rânduri în wp_postmeta
+
30.000 produse x 5 imagini fiecare x 4 metadate per imagine = 600.000 de rânduri în wp_postmeta
Așadar, cu doar 30.000 de produse, ajungi să ai 1.800.000 de rânduri în wp_postmeta
.
Dacă adaugi mai multe proprietăți la produse sau la imaginile produselor, acest număr se va multiplica.
Problema este dublă:
- Self joins sunt foarte costisitoare în MySQL
- Tabela
wp_postmeta
nu este indexată decât dacă folosești versiuni mai noi de MySQL (adică nu există index FULLTEXT pentrumeta_value
)
Ca exemplu dintr-un caz real:
SELECT meta_value FROM wp_postmeta WHERE meta_key LIKE '_shipping_city'
Această interogare care selectează orașul de livrare din toate detaliile comenzilor durează aproximativ ~3 secunde pe un server dedicat de nivel entry, chiar dacă există doar 5-10 comenzi. Acest lucru se întâmplă pentru că interogarea este rulată pe un tabel wp_postmeta
care are ~3 milioane de rânduri într-o instalație live.
Chiar și pagina principală încarcă destul de încet, deoarece tema preia diverse elemente din wp_postmeta
- slide-uri, câteva recenzii, alte metadate. În general, listarea produselor este foarte lentă, iar căutările sunt la fel de lente când afișează produsele.
Nu poți rezolva acest lucru prin metode normale. Poți instala Elastic Search pe server și să folosești un plugin Elastic Search în WordPress, poți utiliza redis/memcached, poți folosi un plugin bun de cache pentru pagini, dar în final problema fundamentală va rămâne - extragerea oricăror date dintr-un tabel wp_postmeta
umflat va fi lentă, oricând s-ar face. Pe serverul unde am testat soluția implementată mai jos, toate acestea erau instalate și configurate corect și optimizate, iar site-ul funcționa acceptabil pentru utilizatorii nelogați sau pentru interogările comune, deoarece plugin-urile de cache intrau în acțiune.
Dar în momentul în care un utilizator logat încerca să facă ceva neobișnuit sau cron-urile, plugin-urile de cache sau orice alt utilitar dorea să extragă date reale din baza de date pentru a le stoca în cache sau pentru a face altceva, lucrurile deveneau extrem de lente.
Așa că am încercat altceva:
Am creat un mic plugin care să mute toate metadatele produselor (postmeta pentru tipul de post product) într-un tabel personalizat generat prin cod. Acest plugin a luat toate metadatele pentru fiecare post și a creat un tabel adăugând fiecare meta ca coloană și inserând valorile în fiecare rând. Am transformat formatul EAV într-un format relațional plat, orizontal. De asemenea, am configurat plugin-ul să șteargă postmeta din toate produsele mutate din tabela wp_postmeta
.
Pe lângă asta, am mutat metadatele pentru tipul de post attachment și pentru toate celelalte tipuri de posturi în tabele proprii.
Apoi, am folosit filtrul get_(post_type)_meta
pentru a suprascrie extragerea metadatelor și a le servi din noile tabele personalizate.
Acum, aceeași interogare de mai sus, care dura ~3 secunde pe wp_postmeta
, durează ~0.006 secunde. Site-ul acum funcționează ca și cum ar fi o instalație fresh de WordPress.
....................
Desigur, abordarea "în stil WordPress" este mai bună. De fapt, este norma.
Totuși, este de asemenea un lucru cunoscut că tabelele EAV sunt foarte ineficiente la scalare. Sunt infinit flexibile și îți permit să stochezi orice date, dar prețul pe care îl plătești pentru asta este performanța. Este un compromis fundamental.
În acest context, este dificil să îi recomanzi cuiva care intenționează să aibă o cantitate imensă de date și - Doamne ferește - să interogheze/caute în acele date să folosească tabela wp_postmeta
cu siguranță. Impactul asupra performanței va fi mare.
Folosirea tabelelor personalizate îți va permite ca datele tale să crească și totuși să rămână suficient de rapide.
Exact cum Pippin Williams, creatorul plugin-ului Easy Digital Downloads, a menționat că ar folosi tabele personalizate dacă ar începe acum să codeze plugin-ul său, dacă intenționezi să creezi ceva care va fi folosit pe termen lung sau va acumula multe date, este mai eficient să folosești tabele personalizate, dacă le proiectezi bine.
Trebuie să te asiguri că orice alt dezvoltator de plugin-uri/addon-uri are mijloace să se conecteze la plugin-ul tău pentru a manipula datele înainte și după extragerea lor. Dacă faci asta, atunci ești pe drumul cel bun.

Lucruri interesante! Un lucru de clarificat este că filtrul menționat "get_(post_type)meta" se numește de fapt "get(meta-type)_metadata", unde meta-type poate fi post, comment sau user. Astfel, get_post_meta() va trece prin filtrul get_post_metadata, indiferent de tipul de post. Valoarea returnată de filtru este cea pe care doriți să fie valoarea finală a metadatelor.

get_(meta-type)_metadata -> într-adevăr funcționează cu toate tipurile de postări, iar funcția finală care este accesată este get_post_metadata. Cu toate acestea, filtrul funcționează atunci când îl utilizați.

Salut @unity100, poți să împărtășești plugin-ul tău? Sau fragmentele cu toate hook-urile pe care le-ai folosit pentru a muta postmeta-ul atașamentelor într-un tabel personalizat? Mulțumesc

@Manu https://wordpress.org/plugins/meta-accelerator/ este ceea ce am testat cu WooCommerce și a funcționat destul de bine. A fost nevoie de unele ajustări pentru diverse scopuri, dar părea că merită efortul. S-ar putea să fi intervenit unele modificări în gestionarea metadatelor de-a lungul anilor și poate necesita actualizări, dar ar fi un bun punct de plecare.

Depinde de ceea ce faci. Modul WordPress este să folosești tabelele existente, deoarece au fost proiectate să fie suficient de flexibile, dar ocazional poți întâlni o nouă clasă de date care nu poate fi plasată într-un tabel existent, de exemplu, dacă ai vrea metadate pentru categorii, ai putea alege să creezi un tabel wp_termsmeta.
Totuși, de obicei poți stoca datele destul de confortabil în diferitele tabele existente, iar locul în care le stochezi depinde de ceea ce face pluginul tău.
- Pentru setări generale ale pluginului, folosește apelul API get_option() - acesta va fi și cache-uit.
- Pentru setări ale pluginului care sunt specifice unui anumit post, folosește metadatele personalizate per post cu get_post_meta(). De obicei, acest lucru este suficient pentru ceea ce ai nevoie.
Cache-ul este implementat în WordPress pentru a accelera timpul de răspuns.

de acord cu Denis 100%. Dar există o soluție pentru această problemă.
Problema cu utilizarea meta datelor postării pentru valori care trebuie interogate apare atunci când valorile sunt tablouri etc. De exemplu:
array(
'key1' => 'val 1',
'key2' => 'val 2'
);
Acest lucru este stocat în baza de date ca un șir serializat, care va arăta cam așa:
{array["key1"]...{}...}
Deci, atunci când doriți să interogați toate postările cu array['key2'] = 'val 2'
, WordPress trebuie să extragă fiecare intrare meta numită array, să o despacheteze, să o testeze și apoi să treacă la următoarea. Acest lucru va afecta negativ performanța serverului dacă site-ul dvs. este popular și are multe postări, pagini, postări personalizate etc.
Soluția depinde de proiect și veți înțelege de ce. Dacă ați stoca datele ca var = val
, atunci WordPress ar putea căuta fără ca PHP să despacheteze fiecare test. Pentru a face acest lucru în scenariul de mai sus, ați folosi un spațiu de nume și ați stoca cheile meta:
_array_key1 = 'val 1';
_array_key2 = 'val 2';
atunci WordPress, căutând key2 cu val2, ar putea extrage direct valoarea. Totuși, această soluție depinde de proiect. Proiectul meu curent necesită stocarea a aproximativ 20 de tipuri de date diferite cu fiecare postare personalizată, așa că metoda de mai sus ar crea doar un tabel masiv de căutat, având în vedere că ne așteptăm la sute de mii de postări. În acest scenariu, un tabel personalizat este singura soluție.
Sper că acest lucru va ajuta pe cineva

Pentru site-ul meu FarmVille :) Am făcut ambele dar nu l-am terminat pentru că l-am vândut:
- Am citit XML-ul de la FarmVille și am încărcat datele într-o tabelă personalizată
- În WordPress am creat câmpuri personalizate automat pentru fiecare câmp din acea tabelă (și câteva extra)
- Apoi a apărut problema ce se întâmplă dacă o valoare se schimbă fie în tabelă, fie pe cealaltă parte: câmpurile personalizate, deoarece ele trebuie să fie continuu sincronizate
Am făcut asta pentru că am vrut pe de o parte ca utilizatorii să poată edita site-ul WordPress introducând noi date FarmVille, de exemplu "o vacă costă 10 monede" DAR din partea de integrare: DACĂ o schimbare în XML înseamnă că vaca acum costă "20 de monede" (prin plugin-ul de editare front-end) aceasta ar fi oferită ca opțiune după: astfel încât fie XML-ul, fie utilizatorul să aibă dreptate (un fel de sistem wiki).
Deci iată un exemplu când folosești ambele.
