¿Cómo extender WP_Query para incluir una tabla personalizada en la consulta?
He estado varios días con este problema. Inicialmente era sobre cómo almacenar los datos de seguidores de un usuario en la base de datos, para lo cual recibí un par de buenas recomendaciones aquí en WordPress Answers. Después de seguir las recomendaciones, he añadido una nueva tabla así:
id leader_id follower_id
1 2 4
2 3 10
3 2 10
En la tabla anterior, la primera fila tiene un usuario con ID 2 que está siendo seguido por un usuario con ID 4. En la segunda fila, un usuario con ID 3 está siendo seguido por un usuario con ID 10. La misma lógica se aplica para la tercera fila.
Ahora, esencialmente quiero extender WP_Query para poder limitar los posts recuperados a aquellos que son únicamente de los líderes de un usuario. Entonces, tomando en cuenta la tabla anterior, si pasara el ID de usuario 10 a WP_Query, los resultados deberían contener solo posts del usuario con ID 2 y del usuario con ID 3.
He buscado mucho tratando de encontrar una respuesta. Tampoco he visto ningún tutorial que me ayude a entender cómo extender la clase WP_Query. He visto las respuestas de Mike Schinkel (extendiendo WP_Query) a preguntas similares, pero realmente no he entendido cómo aplicarlo a mis necesidades. Sería genial si alguien pudiera ayudarme con esto.
Enlaces a las respuestas de Mike según lo solicitado: Enlace 1, Enlace 2

Descargo de responsabilidad importante: la forma correcta de hacer esto NO es modificando la estructura de tu tabla, sino usando wp_usermeta. Así no necesitarás crear consultas SQL personalizadas para consultar tus posts (aunque aún necesitarás algo de SQL personalizado para obtener una lista de todos los que reportan a un supervisor particular - en la sección de Administración, por ejemplo). Sin embargo, dado que el OP preguntó sobre escribir SQL personalizado, aquí está la mejor práctica actual para inyectar SQL personalizado en una consulta existente de WordPress.
Si estás haciendo joins complejos, no puedes simplemente usar el filtro posts_where, porque necesitarás modificar el join, el select, y posiblemente las secciones group by u order by de la consulta también.
Tu mejor opción es usar el filtro 'posts_clauses'. Este es un filtro muy útil (¡que no debe ser abusado!) que te permite añadir/modificar las diversas partes del SQL que es generado automáticamente por las muchas líneas de código en el núcleo de WordPress. La firma del callback del filtro es:
function posts_clauses_filter_cb( $clauses, $query_object ){ }
y espera que devuelvas $clauses
.
Las Cláusulas
$clauses
es un array que contiene las siguientes claves; cada clave es una cadena SQL que será usada directamente en la sentencia SQL final enviada a la base de datos:
- where
- groupby
- join
- orderby
- distinct
- fields
- limits
Si estás añadiendo una tabla a la base de datos (solo haz esto si absolutamente no puedes aprovechar post_meta, user_meta o taxonomías) probablemente necesitarás tocar más de una de estas cláusulas, por ejemplo, fields
(la parte "SELECT" de la sentencia SQL), join
(todas tus tablas, además de la de tu cláusula "FROM"), y quizás orderby
.
Modificando las Cláusulas
La mejor manera de hacer esto es hacer una subreferencia de la clave relevante del array $clauses
que obtuviste del filtro:
$join = &$clauses['join'];
Ahora, si modificas $join
, en realidad estarás modificando directamente $clauses['join']
así que los cambios estarán en $clauses
cuando lo devuelvas.
Preservando las Cláusulas Originales
Es probable (no, en serio, presta atención) que quieras preservar el SQL existente que WordPress generó para ti. Si no, probablemente deberías mirar el filtro posts_request
en su lugar - esa es la consulta mySQL completa justo antes de ser enviada a la base de datos, así que puedes sobrescribirla completamente con la tuya. ¿Por qué querrías hacer esto? Probablemente no quieras.
Así que, para preservar el SQL existente en las cláusulas, recuerda añadir a las cláusulas, no asignarles (es decir: usa $join .= ' {NUEVAS COSAS SQL}';
no $join = '{SOBRESCRIBIR COSAS SQL}';
. Nota que como cada elemento del array $clauses
es una cadena, si quieres añadirle, probablemente querrás insertar un espacio antes de cualquier otro token de caracteres, de lo contrario probablemente crearás algún error de sintaxis SQL.
Puedes asumir que siempre habrá algo en cada una de las cláusulas, y así recordar empezar cada nueva cadena con un espacio, como en: $join .= ' my_table
, o, siempre puedes añadir una pequeña línea que solo añada un espacio si lo necesitas:
$join = &$clauses['join'];
if (! empty( $join ) ) $join .= ' ';
$join .= "JOIN my_table... "; // <-- nota el espacio al final
$join .= "JOIN my_other_table... ";
return $clauses;
Eso es más una cuestión de estilo que otra cosa. Lo importante es recordar: ¡siempre deja un espacio ANTES de tu cadena si estás añadiendo a una cláusula que ya tiene algo de SQL!
Uniendo todo
La primera regla del desarrollo en WordPress es intentar usar tanta funcionalidad del núcleo como puedas. Esta es la mejor manera de asegurar que tu trabajo siga funcionando en el futuro. Supón que el equipo del núcleo decide que WordPress ahora usará SQLite u Oracle u otro lenguaje de base de datos. ¡Cualquier mySQL escrito a mano puede volverse inválido y romper tu plugin o tema! Mejor dejar que WP genere tanto SQL como sea posible por sí mismo, y solo añadir los bits que necesitas.
Así que lo primero es aprovechar WP_Query
para generar tanto de tu consulta base como sea posible. El método exacto que usamos para hacer esto depende en gran medida de dónde se supone que aparece esta lista de posts. Si es una subsección de la página (no tu consulta principal) usarías get_posts()
; si es la consulta principal, supongo que podrías usar query_posts()
y terminarlo, pero la forma correcta de hacerlo es interceptar la consulta principal antes de que llegue a la base de datos (y consuma ciclos del servidor) así que usa el filtro request
.
Bien, así que has generado tu consulta y el SQL está a punto de ser creado. Bueno, de hecho, ya ha sido creado, solo no enviado a la base de datos. Al usar el filtro posts_clauses
, vas a añadir tu tabla de relaciones de empleados a la mezcla. Llamemos a esta tabla {$wpdb->prefix} . 'user_relationship', y es una tabla de intersección. (Por cierto, recomiendo que generalices esta estructura de tabla y la conviertas en una tabla de intersección adecuada con los siguientes campos: 'relationship_id', 'user_id', 'related_user_id', 'relationship_type'; esto es mucho más flexible y potente... pero me desvío).
Si entiendo lo que quieres hacer, quieres pasar un ID de Líder y luego ver solo los posts de los Seguidores de ese Líder. Espero haber entendido bien. Si no es correcto, tendrás que tomar lo que digo y adaptarlo a tus necesidades. Seguiré con tu estructura de tabla: tenemos un leader_id
y un follower_id
. Así que el JOIN será en {$wpdb->posts}.post_author
como clave foránea al 'follower_id' en tu tabla 'user_relationship'.
add_filter( 'posts_clauses', 'filter_by_leader_id', 10, 2 ); // necesitamos el 2 porque queremos obtener todos los argumentos
function filter_by_leader_id( $clauses, $query_object ){
// No sé cómo pretendes pasar el leader_id, así que asumamos que es un global
global $leader_id;
// En este ejemplo solo quiero afectar una consulta en la página de inicio.
// Aquí es donde se usa $query_object, para ayudarnos a evitar afectar
// TODAS las consultas (ya que TODAS las consultas pasan por este filtro)
if ( $query_object->is_home() ){
// Ahora, añadamos tu tabla al SQL
$join = &$clauses['join'];
if (! empty( $join ) ) $join .= ' '; // añade un espacio solo si es necesario (¡para nota!)
$join .= "JOIN {$wpdb->prefix}employee_relationship EMP_R ON EMP_R.follower_id = {$wpdb->posts}.author_id";
// Y asegurémonos de añadirlo a nuestros criterios de selección
$where = &$clauses['where'];
// En cualquier caso, siempre empiezas con AND, porque siempre hay una declaración '1=1' como la primera declaración de la cláusula WHERE que WP añade.
// ¡Solo no olvides el espacio inicial!
$where .= " AND EMP_R.leader_id={$leader_id}"; // asumiendo que $leader_id es siempre (int)
// Y asumo que querrás los posts "agrupados" por id de usuario, así que modifiquemos la cláusula groupby
$groupby = &$clauses['groupby'];
// Necesitamos anteponer, así que...
if (! empty( $groupby ) ) $groupby = ' ' . $groupby; // Para los presumidos
$groupby = "{$wpdb->posts}.post_author" . $groupby;
}
// En cualquier caso, necesitamos devolver nuestras cláusulas...
return $clauses;
}

Estoy respondiendo a esta pregunta extremadamente tarde y me disculpo por ello. He estado demasiado ocupado con plazos de entrega para atender esto.
Un gran agradecimiento a @m0r7if3r y @kaiser por proporcionar las soluciones base que pude extender e implementar en mi aplicación. Esta respuesta proporciona detalles sobre mi adaptación de las soluciones ofrecidas por @m0r7if3r y @kaiser.
Primero, permítanme explicar por qué se hizo esta pregunta en primer lugar. A partir de la pregunta y los comentarios de la misma, se podría deducir que estoy intentando que WP_Query obtenga publicaciones de todos los usuarios (líderes) que un usuario dado (seguidor) sigue. La relación entre el seguidor y el líder se almacena en una tabla personalizada follow
. La solución más común a este problema es extraer los ID de usuario de todos los líderes de un seguidor de la tabla follow y colocarlos en un array. Ver abajo:
global $wpdb;
$results = $wpdb->get_results($wpdb->prepare('SELECT leader_id FROM cs_follow WHERE follower_id = %s', $user_id));
foreach($results as $result)
$leaders[] = $result->leader_id;
Una vez que tienes el array de líderes, podrías pasarlo como argumento a WP_Query. Ver abajo:
if (isset($leaders)) $authors = implode(',', $leaders); // Necesario ya que el argumento authors de WP_Query solo acepta una cadena con ID de autores de publicaciones separados por comas
$args = array(
'post_type' => 'post',
'posts_per_page' => 10,
'author' => $authors
);
$wp_query = new WP_Query( $args );
// El bucle normal de WordPress continúa
La solución anterior es la forma más sencilla de lograr los resultados deseados. Sin embargo, no es escalable. En el momento en que un seguidor sigue a decenas de miles de líderes, el array resultante de ID de líderes se volvería extremadamente grande y obligaría a tu sitio WordPress a usar 100MB - 250MB de memoria en cada carga de página, eventualmente colapsando el sitio. La solución al problema es ejecutar una consulta SQL directamente en la base de datos y obtener las publicaciones relevantes. Fue entonces cuando la solución de @m0r7if3r vino al rescate. Siguiendo la recomendación de @kaiser, me dispuse a probar ambas implementaciones. Importé alrededor de 47K usuarios desde un archivo CSV para registrarlos en una instalación de prueba limpia de WordPress. La instalación ejecutaba el tema Twenty Eleven. Luego, ejecuté un bucle for para que unos 50 usuarios siguieran a todos los demás. La diferencia en el tiempo de consulta para ambas soluciones fue asombrosa. La solución de @kaiser normalmente tomaba entre 2 y 5 segundos para cada consulta. La variación, supongo, ocurre porque WordPress almacena en caché las consultas para su uso posterior. Por otro lado, la solución de @m0r7if3r demostró un tiempo de consulta promedio de 0.02 ms. Para probar ambas soluciones, tenía la indexación ACTIVADA para la columna leader_id. Sin indexación, hubo un aumento dramático en el tiempo de consulta.
El uso de memoria con la solución basada en arrays fue de alrededor de 100-150 MB y bajó a 20 MB al ejecutar un SQL directo.
Tuve un problema con la solución de @m0r7if3r cuando necesitaba pasar el ID del seguidor a la función de filtro posts_where. Al menos, según mi conocimiento, WordPress no permite ningún medio para pasar una variable a las funciones de filtro. Puedes usar variables Globales, aunque quería evitarlas. Terminé extendiendo WP_Query para finalmente abordar el problema. Así que aquí está la solución final que implementé (basada en la solución de @m0r7if3r).
class WP_Query_Posts_by_Leader extends WP_Query {
var $follower_id;
function __construct($args=array()) {
if(!empty($args['follower_id'])) {
$this->follower_id = $args['follower_id'];
add_filter('posts_where', array($this, 'posts_where'));
}
parent::query($args);
}
function posts_where($where) {
global $wpdb;
$table_name = $wpdb->prefix . 'follow';
$where .= $wpdb->prepare(" AND post_author IN (SELECT leader_id FROM " . $table_name . " WHERE follower_id = %d )", $this->follower_id);
return $where;
}
}
$args = array(
'post_type' => 'post',
'posts_per_page' => 10,
'follower_id' => $follower_id
);
$wp_query = new WP_Query_Posts_by_Leader( $args );
Nota: Finalmente probé la solución anterior con 1.2 millones de entradas en la tabla follow. El tiempo promedio de consulta se mantuvo alrededor de 0.060 ms.

Puedes hacer esto con una solución completamente en SQL usando el filtro posts_where
. Aquí tienes un ejemplo de cómo hacerlo:
if( alguna condición )
add_filter( 'posts_where', 'wpse50305_leader_where' );
// lol, el ID de la pregunta es el mismo hacia adelante y hacia atrás
function wpse50305_leader_where( $where ) {
$where .= $GLOBALS['wpdb']->prepare( ' AND post_author '.
'IN ( '.
'SELECT leader_id '.
'FROM custom_table_name '.
'WHERE follower_id = %s'.
' ) ', $follower_id );
return $where;
}
Creo que también puede haber una forma de hacer esto con un JOIN
, pero no se me ocurre cómo. Seguiré probando y actualizaré la respuesta si lo consigo.
Alternativamente, como sugirió @kaiser, puedes dividirlo en dos partes: obtener los líderes y hacer la consulta. Tengo la sensación de que esto podría ser menos eficiente, pero sin duda es la forma más comprensible de hacerlo. Tendrías que probar la eficiencia por ti mismo para determinar qué método es mejor, ya que las consultas SQL anidadas pueden volverse bastante lentas.
DE LOS COMENTARIOS:
Deberías colocar la función en tu functions.php
y hacer el add_filter()
justo antes de que se llame al método query()
de WP_Query
. Inmediatamente después, deberías hacer un remove_filter()
para que no afecte a las demás consultas.

Editaste tu A y agregué prepare()
. Espero que no te moleste la edición. Y sí: el rendimiento tiene que ser medido por OP. De todos modos: sigo pensando que esto debería ser simplemente usermeta y nada más.

@m0r7if3r Gracias por intentar una solución. Acabo de publicar un comentario en respuesta a la respuesta de kaiser, con preocupaciones sobre posibles problemas de escalabilidad. Por favor, tómalo en consideración.

@m0r7if3r Estuve probando esto y encuentro que hace que WP_Query devuelva todas las publicaciones. Por todas las publicaciones me refiero a publicaciones de todos los tipos (posts, adjuntos, páginas) de todos los usuarios. Este es el SQL que echo $wp_query->request;
muestra para la consulta SQL_CALC_FOUND_ROWS cs_posts.* FROM cs_posts INNER JOIN cs_postmeta ON (cs_posts.ID = cs_postmeta.post_id) WHERE 1=1 GROUP BY cs_posts.ID ORDER BY cs_posts.post_date DESC LIMIT 0, 30

Olvidé poner return $where;
. ¿El código actualizado corrige el problema?

¡Uf! Los errores tipográficos. Sí, ahora funciona. Ahora probaré implementar la solución de @kaiser y medir los tiempos. Por cierto, un efecto secundario que veo al usar esto es que comienza a afectar todas las consultas si hay múltiples en una sola página. Además, esto rompió las reescrituras de URL para mí cuando lo coloqué en functions.php. Mover el código justo antes de la llamada WP_Query eliminó la interferencia con las reescrituras de URL. Aunque no estoy seguro si colocarlo antes de WP_Query sería una mala práctica???

Deberías poner la función en tu functions.php
y hacer el add_filter()
justo antes de que se llame al método query()
de WP_Query
. Inmediatamente después, deberías hacer remove_filter()
para que no afecte a las otras consultas. No estoy seguro de cuál sería el problema con la reescritura de URL, he usado posts_where
en muchas ocasiones y nunca he visto eso...

@m0r7if3r Gracias por señalarlo. Continuando con las pruebas en los datos de muestra. Te mantendré informado.

Etiqueta de Plantilla
Simplemente coloca ambas funciones en tu archivo functions.php
. Luego ajusta la primera función y añade el nombre de tu tabla personalizada. Necesitarás algo de prueba/error para eliminar el ID del usuario actual dentro del array resultante (ver comentario).
/**
* Obtener los "Líderes" del usuario actual
* @param int $user_id El ID del usuario actual
* @return array $query Los líderes
*/
function wpse50305_get_leaders( $user_id )
{
global $wpdb;
return $wpdb->query( $wpdb->prepare(
"
SELECT `leader_id`, `follower_id`
FROM %s
WHERE `follower_id` = %s
ORDERBY `leader_id` ASC
",
// Edita el nombre de la tabla
"{$wpdb->prefix}nombre_tabla_personalizada",
$user_id
) );
}
/**
* Obtener un array de posts que contienen publicaciones
* de los "Líderes" que el usuario actual sigue
* @return array $posts Posts que son del "Líder" actual
*/
function wpse50305_list_posts_by_leader()
{
get_currentuserinfo();
global $current_user;
$user_id = $current_user->ID;
$leaders = wpse50305_get_leaders( $user_id );
// podría ser que necesites iterar sobre $leaders
// y eliminar los IDs de seguidores
return get_posts( array(
'author' => implode( ",", $leaders )
) );
}
Dentro de la plantilla
Aquí puedes hacer lo que quieras con tus resultados.
foreach ( wpse50305_list_posts_by_leader() as $post )
{
// haz algo con $post
}
NOTA No tenemos datos de prueba, etc. así que lo anterior es un poco de adivinanza. Asegúrate de que tú edites esta respuesta con lo que te funcionó, para tener un resultado satisfactorio para futuros lectores. Aprobaré la edición en caso de que tengas muy baja reputación. También puedes borrar esta nota después. Gracias.

Gracias por proporcionar una solución. Sin embargo, este enfoque tiene advertencias en términos de escalabilidad. El problema está en obtener el ID de los líderes como un array. El array podría volverse extremadamente grande dependiendo del número de usuarios que un usuario siga. He visto que esto consume una gran cantidad de memoria. Esto debe hacerse como una única consulta SQL que podría implicar realizar un JOIN en la tabla personalizada y la tabla de posts de WP y obtener los resultados. La mejor solución es extender WP_Query. El problema, no sé cómo hacerlo. Nuevamente gracias por intentarlo. Se agradecen comentarios.

JOIN
es mucho más costoso. Además: Como mencioné, no tenemos datos de prueba, así que por favor prueba ambas respuestas e ilumínanos con tus resultados.

WP_Query en sí mismo trabaja con JOINs entre la tabla de posts y postmeta al realizar consultas. He visto que el uso de memoria en PHP aumenta a 70MB - 200MB por carga de página. Ejecutar algo así con muchos usuarios simultáneos requeriría una infraestructura extrema. Mi suposición sería que, dado que WordPress ya implementa una técnica similar, los JOINs deberían ser menos gravosos en comparación con trabajar con un array de IDs.

No, lo siento, pero no. También hay intentos de eliminar los JOIN
adicionales. Ejemplo: WP añade un JOIN
de la tabla de términos al consultar múltiples taxonomías, lo cual es contraproducente para el rendimiento. Simplemente usa timer_start()
antes de la consulta y timer_stop()
después para medir la diferencia.

Además: Asegúrate de tener el INDEX
en la columna correcta. Esto hace una diferencia enorme en el rendimiento. En un proyecto que realicé, donde necesitaba hacer consultas por lat/lng, tengo una tabla adicional que es pequeña, bien indexada y funciona mucho más rápido que cualquier JOIN
que probé. Para un conocimiento más profundo sobre los JOIN
s, lee esto.

¿Entonces tu consejo sería quedarse con el array en lugar de ejecutar un SQL con join, correcto?

Sí, pero como dijo @m0r7if3r: Mídelo. Te di los enlaces. Solo tienes que cronometrarlo y mostrarnos qué obtuviste. Si no lo haces, bueno, entonces hemos hecho muchas bonitas suposiciones :)

Definitivamente estoy probando ambas soluciones. Pero, para cronometrar esto, tendré que volcar la base de datos con datos de prueba, lo que tomará unas horas (poblaré la db con 50k usuarios). Lo haré y les mantendré informados.

Ok, aquí están los resultados de las pruebas. Para esto, agregué alrededor de 47K usuarios desde un archivo csv. Luego, ejecuté un bucle for para que los primeros 45 usuarios siguieran a todos los demás usuarios. Esto resultó en 3,704,951 registros guardados en mi tabla personalizada. Inicialmente, la solución de @m0r7if3r me dio un tiempo de consulta de 95 segundos, que bajó a 0.020 ms después de activar la indexación en la columna leader_id. El total de memoria PHP consumida fue de alrededor de 20MB. Por otro lado, tu solución tomó entre 2 y 5 segundos para la consulta con indexación activada. El total de memoria PHP consumida fue de alrededor de 117MB.

@John ¿Podrías agregar esto como una respuesta separada? Realmente me alegra que hayas hecho esta prueba y nos hayas proporcionado datos reales. :) Lo que apreciaría aún más es una prueba más "realista": Que cada usuario siga a un$leader_amount = rand( 0, 5 );
y luego agregar el número de$leader_amount
x$random_ids = rand( 0, 47000 );
a cada usuario. Hasta ahora, lo que sabemos es: Mi solución sería extremadamente mala si un usuario sigue a todos los demás usuarios. Además: Tendrás que mostrar cómo hiciste la prueba y dónde exactamente agregaste los temporizadores. Esta es una pregunta única y realmente me gusta :)

@John Enlacé y compartí tu pregunta y participación, y quería mencionar que todos están disfrutando mucho tu trabajo en ello (mira los votos positivos). :)

Realizaré más pruebas basándome en tu sugerencia para hacer una prueba más realista y luego escribiré una respuesta con detalles. Sin embargo, para una prueba verdaderamente realista hay otras dos variables que actualmente no puedo simular. Primero: Al menos 20 publicaciones por cada usuario. Actualmente, solo probé con alrededor de 50 publicaciones entre 2-3 usuarios. Segundo: Un gran tráfico. Estas dos variables también afectarían los resultados obtenidos en la vida real.

Estaba investigando más tu sugerencia para aleatorizar lo siguiente. Sin embargo, no pude entender los fragmentos de código que escribiste. Un fragmento completo sería más útil. Esto es lo que había hecho para que los primeros 50 usuarios siguieran a todos los demás: for ($j = 2; $j <= 52; $j++) { for ($i = ($j + 1); $i <= 47000; $i++) { $rows_affected = $wpdb->insert($table_name, array( 'leader_id' => $i, 'follower_id' => $j)); } }
. Un bucle for anidado muy simple.

Sobre el tráfico) Podrías usar las últimas versiones de apache, nginx, minificación, caché, compresión, etc. Cada una tendría un impacto en los resultados. Pero buscamos resultados en un entorno local, básico (sin plugins, tema por defecto) que pueda realmente ser ponderado.

¿Recibiste una notificación de mi comentario sobre la aleatorización? Supongo que olvidé mencionar tu nombre en el comentario. De todos modos, supongo que el dueño del post fue notificado. Esperaré tus comentarios antes de proceder.

Nota: Esta respuesta es para evitar discusiones extendidas en los comentarios
Aquí está el código del OP de los comentarios, para agregar el primer conjunto de usuarios de prueba. Debe modificarse a un ejemplo del mundo real.
for ( $j = 2; $j <= 52; $j++ ) { for ( $i = ($j + 1); $i <= 47000; $i++ ) { $rows_affected = $wpdb->insert( $table_name, array( 'leader_id' => $i, 'follower_id' => $j ) ); } }
OP Sobre la Prueba Para esto agregué alrededor de 47K usuarios desde un archivo csv. Luego, ejecuté un bucle for para que los primeros 45 usuarios siguieran a todos los demás usuarios.
- Esto resultó en 3,704,951 registros guardados en mi tabla personalizada.
- Inicialmente, la solución de @m0r7if3r me dio un tiempo de consulta de 95 segs, que bajó a 0.020 ms después de activar la indexación en la columna leader_id. La memoria total de PHP consumida fue alrededor de 20MB.
- Por otro lado, tu solución tomó entre 2 a 5 segs para la consulta con indexación activada. La memoria total de PHP consumida fue alrededor de 117MB.
Mi respuesta a esta ↑ prueba:
una prueba más "realista": Que cada usuario siga a
$leader_amount = rand( 0, 5 );
y luego agregar el número de$leader_amount x $random_ids = rand( 0, 47000 );
a cada usuario. Hasta ahora lo que sabemos es: Mi solución sería extremadamente mala si un usuario sigue a todos los demás. Además: Debes mostrar cómo hiciste la prueba y dónde exactamente agregaste los temporizadores.También debo mencionar que el ↑ seguimiento de tiempos anterior no se puede medir realmente, ya que también tomaría el tiempo para calcular el bucle. Sería mejor recorrer el conjunto resultante de IDs en un segundo bucle.
proceso adicional aquí

Nota para quienes han estado siguiendo esta Q: Estoy en proceso de medir el rendimiento bajo varias condiciones y publicaré los resultados en un día o 3. Esta ha sido una tarea extremadamente consumidora de tiempo debido a la escala de datos de prueba que necesitan ser generados.
