Post meta vs tablas separadas en la base de datos
Cuando se desarrollan plugins que requieren almacenamiento de datos, ¿cuáles son los pros y contras de usar uno u otro método?
La explicación dada en el codex no es detallada:
Antes de lanzarse a crear una tabla completamente nueva, sin embargo, considere si almacenar los datos de su plugin en Post Meta de WordPress (también conocido como Campos Personalizados) funcionaría. Post Meta es el método preferido; úselo cuando sea posible/práctico.

Bueno, si me pongo el sombrero de un script kiddie de WP, mi respuesta sería: usa post_meta, siempre.
Sin embargo, resulta que sé un par de cosas sobre bases de datos, así que mi respuesta es: nunca, jamás, bajo ninguna circunstancia, uses un modelo EAV (es decir, la tabla post_meta) para almacenar datos que puedas necesitar consultar.
En cuanto a índices, básicamente no hay ninguno útil en las tablas meta. Así que si estás almacenando datos tipo XYZ y esperas consultar todos los posts que tengan XYZ con un valor de 'abc'
, bueno... buena suerte. (Mira todos los tickets relacionados con usuarios/roles/capacidades en el trac de WP para hacerte una idea de lo espantoso que puede llegar a ser).
En cuanto a joins, rápidamente chocas contra el límite en el que el optimizador decide usar un algoritmo genérico en lugar de analizar la consulta cuando hay múltiples criterios de unión.
Así que no, no, no, no. Nunca, jamás, nunca uses una tabla meta. A menos que lo que estés almacenando sea cosmético y nunca vaya a ser parte de un criterio de consulta.
Se reduce a tu aplicación. Si estás almacenando, digamos, la fecha de nacimiento de un director de cine, no hay problema. Usa post_meta todo lo que quieras. Pero si estás almacenando, digamos, la fecha de estreno de una película, estarías loco si no usas una tabla separada (o agregas columnas a la tabla posts) y añades un índice a esa columna.

Sí, los plugins que estoy desarrollando manejan datos personalizados como eventos, noticias, comunicados de prensa, ofertas de trabajo... Desde fuera del "Mundo WordPress", usar tablas no es realmente una opción. Pero el consejo del WordPress Codex es un poco confuso. ¿Cómo pueden preferirse fragmentos de datos serializados frente a datos normalizados/estructurados/indexados?

Si le preguntas al desarrollador promedio de WP, probablemente responderá "usa un meta" o "usa una taxonomía". Y estoy de acuerdo, hasta el punto en que necesites consultar contra esos datos. Si es así, y creo que es tu caso, mi única respuesta es: añade los campos a la tabla de posts o crea una tabla separada. De lo contrario, te enfrentarás a enormes problemas de rendimiento cuando se trate de consultas y, aún más importante para listas de nodos, ordenación top-n.

Denis, ¿podrías elaborar un poco más sobre esto? Lo encuentro muy informativo pero me encantaría tener más datos, ¿alguien ha hecho pruebas? ¿Cuáles son exactamente las principales desventajas y limitaciones? Gracias.

@Denis - Bastante apasionada tu defensa en contra del postmeta, ¿eh? Sabes que vas en contra de la ortodoxia y caerás de la gracia de los sumos sacerdotes de la iglesia de la poesía del código si persistes en ese discurso, ¿verdad? :-) Pero en serio, ¿no crees que exageras un poco? Realmente depende de si habrá decenas de miles de registros meta o no. En muchos casos simplemente no hay suficientes registros para preocuparse. Un sitio complejo que estoy implementando tiene alrededor de 10,000 registros meta con pocos registros nuevos planeados, y funciona bien (por cierto, no es un blog).

@Mike: 10k filas no es mucho. Cuando pruebo el esquema que uso, lo lleno con 200k filas y me aseguro de que ninguna consulta común tarde más de unos pocos ms. Esto incluye las relacionadas con el (grafo orientado de) permisos, añadiría. Para más detalles sobre pros y contras, te remito a un hilo de SO: http://stackoverflow.com/questions/870808/entity-attribute-value-database-vs-strict-relational-model-ecommerce-question

@Mike: tampoco me malinterpretes. Uso EAV aquí y allá. Simplemente, me limito a almacenar datos no esenciales en él, con índices parciales en la tabla meta para el campo ocasional que realmente lo necesita pero claramente no pertenece a la tabla principal (un token de un solo uso o una preferencia booleana vienen a la mente).

@Denis - Gracias por los comentarios. Y no me malinterpretes, probablemente me inclino mucho más hacia tu perspectiva, pero la combinación de 1.) un debate de una hora con Matt en WordCamp Birmingham sobre los méritos de campos tipo Pods y 2.) la simplicidad de meta me ha hecho resignarme a enfocar mi atención en otros temas que potencialmente podría cambiar. En WCB me di cuenta de que mientras Matt esté a cargo esto no cambiará porque (supongo) Matt está tan enamorado de la idea de menos tablas que no se permite reconocer las desventajas de indexar con una clave de 768 bytes. <suspirar>

Jeje. Creo que te encantará el CMS que estoy desarrollando, si termino lanzando parte o todo al público. :-)

Si tu plugin va a manejar MUCHOS datos, entonces usar la tabla wp_postmeta
NO es una buena idea, como se demuestra a continuación:
Tomando WooCommerce como ejemplo, en una tienda con ~30,000 productos, habrá un promedio de, digamos, ~40 metadatos por producto (atributos y todo), 5 imágenes por producto, lo que significa que habrá ~4 metadatos por cada imagen:
30,000 productos x 40 metadatos cada uno = 1,200,000 filas en wp_postmeta
+
30,000 productos x 5 imágenes cada uno x 4 metadatos por imagen = 600,000 filas en wp_postmeta
Así que con apenas 30,000 productos, estarás manejando 1,800,000 filas en wp_postmeta
.
Si agregas más propiedades a tus productos o a las imágenes de tus productos, este número se multiplicará.
El problema con esto es doble:
- Los self joins son muy costosos en MySQL
- La tabla
wp_postmeta
no está indexada a menos que uses versiones recientes de MySQL (es decir, no hay índice FULLTEXT parameta_value
)
Para dar un ejemplo de un caso real:
SELECT meta_value FROM wp_postmeta WHERE meta_key LIKE '_shipping_city'
Esta consulta, que selecciona la ciudad de envío de todos los detalles de pedidos, tarda ~3 segundos en un servidor dedicado de nivel básico, incluso si solo hay 5-10 pedidos. Esto se debe a que la consulta se ejecuta en una tabla wp_postmeta
que tiene ~3 millones de filas en la instalación en producción.
Incluso la página de inicio carga bastante lento, porque el tema extrae varios elementos de wp_postmeta
: sliders, algunas reseñas, algunos otros metadatos. En general, la lista de productos es muy lenta, y las búsquedas son igualmente lentas al listar productos.
No puedes solucionar esto mediante métodos normales. Puedes instalar Elastic Search en tu servidor y usar un plugin de Elastic Search en WordPress, puedes usar redis/memcached, puedes usar un buen plugin de caché de páginas, pero al final el problema fundamental permanecerá: recuperar cualquier cantidad de datos de una tabla wp_postmeta
inflada será lento, sin importar cuándo se haga. En el servidor donde probé la solución que implementé a continuación, todos estos estaban instalados, configurados y optimizados correctamente, y el sitio funcionaba aceptablemente bien para usuarios no logueados o consultas comunes, ya que los plugins de caché entraban en acción.
Pero en el momento en que un usuario logueado intentaba hacer algo no común o los crons, plugins de caché o cualquier otra utilidad necesitaba recuperar datos reales de la base de datos para almacenarlos en caché o hacer cualquier otra cosa, todo se volvía extremadamente lento.
Así que probé algo diferente:
Programé un pequeño plugin para llevar todos los metadatos de productos (postmeta para el tipo de publicación product) a una tabla personalizada generada por código. Este plugin tomaba todos los metadatos de cada publicación y creaba una tabla añadiendo cada metadato como columnas e insertando los valores en cada fila. Convertí el formato EAV en un formato relacional plano y horizontal. También hice que el plugin eliminara los postmeta de todos los productos movidos de la tabla wp_postmeta
.
Mientras lo hacía, también moví los metadatos de attachment y los metadatos de otros tipos de publicación a sus propias tablas.
Luego, enganché el filtro get_(post_type)_meta
para anular la recuperación de metadatos y servirlos desde las nuevas tablas personalizadas.
Ahora la misma consulta de antes, que tardaba ~3 segundos en recuperar datos de wp_postmeta
, tarda ~0.006 segundos. El sitio ahora se comporta como si fuera una instalación fresca de WordPress.
....................
Naturalmente, hacer las cosas al estilo de WordPress es mejor. De hecho, es la norma.
Sin embargo, también es un conocimiento obvio que una tabla EAV es muy ineficiente para escalar. Es infinitamente flexible y te permite almacenar cualquier dato, pero el precio que pagas por eso es el rendimiento. Es una compensación fundamental.
En ese contexto, es difícil decirle a alguien que planea manejar una gran cantidad de datos y —Dios no lo quiera— consultar/buscar en esos datos que use la tabla wp_postmeta
con seguridad. El impacto en el rendimiento será enorme.
Usar tus propias tablas personalizadas permitirá que tus datos se acumulen y aún así sigan siendo lo suficientemente rápidos.
Al igual que Pippin Williams, el creador del plugin Easy Digital Downloads, mencionó que usaría tablas personalizadas si estuviera comenzando a codificar su plugin hoy, si vas a crear algo que se usará durante mucho tiempo o acumulará muchos datos, es más eficiente usar tus propias tablas si las diseñas bien.
Debes asegurarte de que cualquier otro desarrollador de plugins/addons tenga medios para engancharse a tu plugin y manipular tus datos antes y después de su recuperación. Si haces eso, entonces estarás bastante sólido.

¡Cosas interesantes! Una cosa para aclarar es que el filtro mencionado "get_(post_type)meta" en realidad se llama "get(meta-type)_metadata", donde meta-type puede ser post, comment o user. Así que get_post_meta() pasará por el filtro get_post_metadata, independientemente del tipo de publicación. El valor de retorno del filtro es lo que deseas que sea el valor final del meta.

get_(meta-type)_metadata -> de hecho funciona con todos los tipos de publicación, y efectivamente la función final que se visita es get_post_metadata. Sin embargo, el filtro funciona cuando lo usas de todos modos.

Hola @unity100 ¿puedes compartir tu plugin? o los fragmentos de todos los hooks que usaste para mover el postmeta de adjuntos a una tabla personalizada? Gracias

@Manu https://wordpress.org/plugins/meta-accelerator/ fue lo que probé con WooCommerce, y funcionó lo suficientemente bien. Necesitó algunos ajustes para varios propósitos pero parecía que valía la pena. Puede que haya habido algunos cambios en el manejo de meta a lo largo de los años y que necesite actualizaciones, pero sería un buen punto de partida.

Depende de lo que estés haciendo. La forma de WP es utilizar las tablas existentes, ya que han sido diseñadas para ser lo suficientemente flexibles, sin embargo ocasionalmente podrías encontrarte con una nueva clase de datos que no puede ser colocada en una tabla existente, por ejemplo, si quisieras metadatos para categorías, podrías elegir crear una tabla wp_termsmeta.
Sin embargo, usualmente puedes almacenar tus datos cómodamente en las diferentes tablas que existen, y dónde almacenes tus datos depende de lo que haga tu plugin.
- Para configuraciones generales del plugin, usa la llamada API get_option() - esto también será cacheado.
- Para configuraciones del plugin que sean particulares a un post individual, entonces usa los metadatos personalizados por post con get_post_meta(). Esto usualmente es suficiente para lo que necesitas.
El caché está implementado dentro de WordPress para acelerar también tu tiempo de respuesta.

Estoy completamente de acuerdo con Denis. Pero hay una forma de solucionarlo.
El problema de usar meta campos para valores que serán consultados es cuando los valores son arrays, etc. Como en este ejemplo:
array(
'key1' => 'val 1',
'key2' => 'val 2'
);
Esto se almacena en la base de datos como una cadena serializada, que se verá algo así:
{array["key1"]...{}...}
Entonces, cuando quieras consultar todos los posts con array['key2'] = 'val 2'
, WordPress tiene que recuperar cada entrada meta llamada 'array', deserializarla, evaluarla y pasar a la siguiente. Esto definitivamente ralentizará tu servidor si tu sitio tiene éxito y contiene muchos posts, páginas, custom posts, etc.
La solución depende del proyecto, y verás por qué. Si almacenas los datos como var = val
, WordPress podrá buscarlos sin necesidad de que PHP desempaquete cada prueba. Para hacer esto en el escenario anterior, usarías algún tipo de namespacing y almacenarías las meta claves así:
_array_key1 = 'val 1';
_array_key2 = 'val 2';
Entonces, cuando WordPress busque la clave 2 con valor 'val 2', podrá recuperarla directamente. Sin embargo, esto depende del proyecto. Mi proyecto actual requiere almacenar unos 20 tipos diferentes de datos con cada custom post, por lo que el método anterior simplemente crearía una tabla enorme para buscar, considerando que esperamos tener cientos de miles de posts. En ese escenario, una tabla personalizada es la única solución.
Espero que esto le ayude a alguien

Para mi sitio de FarmVille :) Hice ambas cosas pero nunca lo terminé porque lo vendí:
- Leí el XML de FarmVille y volqué los datos en una tabla personalizada
- En WordPress tenía campos personalizados creados automáticamente para cada campo en esa tabla (y algunos extras)
- Ahora la preocupación es qué pasa si un valor cambia tanto en la tabla como en el otro lado: el campo personalizado ya que necesitan estar continuamente sincronizados
Hice esto porque quería por un lado que los usuarios editaran el sitio WordPress introduciendo nuevos datos de FarmVille, por ejemplo "una vaca cuesta 10 monedas" PERO desde el lado de la integración: SI un cambio en el XML significaba que la vaca ahora cuesta "20 monedas" (a través del plugin de edición front-end), eso se daría como opción después: de modo que O el XML O el usuario tuviera razón (una especie de sistema wiki).
Así que aquí hay un ejemplo cuando se usan ambos.
