CodeGym /Cursos /Módulo 5. Spring /Lección 299: Optimización de consultas en GraphQL

Lección 299: Optimización de consultas en GraphQL

Módulo 5. Spring
Nivel 16 , Lección 8
Disponible

GraphQL — es una herramienta potente que permite a los clientes pedir solo los datos que necesitan. Sin embargo, con el aumento de la complejidad de las consultas y el crecimiento del esquema, GraphQL puede empezar a ejercer una presión significativa sobre el rendimiento del servidor y las bases de datos. Veamos cómo pueden manifestarse los problemas de rendimiento en GraphQL:

  • Problema N+1: uno de los escenarios más comunes, cuando una consulta a un campo provoca múltiples consultas similares a la base de datos. Por ejemplo, si pides usuarios y sus posts, puedes hacer una consulta para obtener todos los usuarios y luego, para cada usuario, ejecutar una consulta separada para obtener los posts — eso es el "N+1".
  • Consultas profundas: los clientes pueden solicitar datos con una profundidad y anidamiento excesivos, lo que puede sobrecargar el servidor.
  • Falta de caché: si no optimizamos las consultas y no usamos caché, esto puede afectar a los recursos tanto del servidor como de la base de datos.
  • Duplicación de datos: la misma consulta puede ejecutarse varias veces si no procesamos los datos por lotes.

Es importante resolver todos estos problemas para crear una API GraphQL responsiva y escalable.


Herramientas para análisis y optimización de consultas

Para entender cómo las consultas afectan al rendimiento, puedes usar las siguientes herramientas y métodos de análisis:

1. Perfilado de consultas:

  • Activa el logging de consultas en la base de datos para ver cuánto tarda cada consulta SQL.
  • Usa librerías como Spring Actuator para monitorizar métricas de tu aplicación.

2. Herramientas de GraphQL para analizar consultas:

  • GraphQL Engine (por ejemplo, Apollo Engine) — ayuda a perfilar y analizar la ejecución de tus consultas GraphQL.
  • Herramientas de trazabilidad como Sentry o OpenTelemetry, para monitorización distribuida.

3. DataLoader:

  • DataLoader — es una librería que se usa para batchear y deduplicar consultas.
  • Es la salvación contra el problema N+1 en GraphQL. Hablamos de esto en la lección anterior.

4. Límites y restricciones:

  • Limita la profundidad de las consultas y su complejidad para evitar el abuso de tu API.
  • Usa librerías como graphql-depth-limit para restringir la profundidad de las consultas.

Métodos de optimización de consultas

Pasemos a recomendaciones prácticas para optimizar consultas en GraphQL.

Uso de DataLoader para batching

DataLoader te ayuda a combinar varias consultas a la base de datos en una sola. Veamos un ejemplo donde pides usuarios y sus posts:

query {
  users {
    id
    name
    posts {
      id
      title
    }
  }
}

Sin DataLoader esto llevará a lo siguiente:

  1. Una consulta para obtener todos los usuarios.
  2. N consultas para obtener los posts de cada usuario.

Con DataLoader puedes batchear las consultas de posts, combinándolas en una sola consulta.

Ejemplo de implementación de DataLoader:


@Bean
public DataLoaderRegistry dataLoaderRegistry(PostService postService) {
    DataLoaderRegistry registry = new DataLoaderRegistry();

    DataLoader<Long, List<Post>> postDataLoader = DataLoader.newMappedDataLoader(userIds ->
        CompletableFuture.supplyAsync(() -> postService.getPostsByUserIds(userIds))
    );

    registry.register("postLoader", postDataLoader);
    return registry;
}

@DataFetcher
public DataFetcher<List<Post>> postsFetcher(DataLoaderRegistry dataLoaderRegistry) {
    return environment -> {
        DataLoader<Long, List<Post>> dataLoader = dataLoaderRegistry.getDataLoader("postLoader");
        Long userId = environment.getSource();
        return dataLoader.load(userId); // Cargamos los datos de forma asíncrona
    };
}

Cuando pides usuarios y sus posts, las consultas a la base de datos se unirán.

2. Limitar la profundidad y la complejidad de las consultas

Para evitar una presión excesiva en el servidor, limita la profundidad de las consultas y su complejidad. Por ejemplo:


query {
  user {
    posts {
      comments {
        author {
          friends {
            posts { ... }
          }
        }
      }
    }
  }
}

Una consulta así puede ser extremadamente cara de ejecutar.

Ejemplo de limitación de profundidad en Spring GraphQL:

Usamos una librería, por ejemplo graphql-depth-limit:

GraphQL graphQL = GraphQL.newGraphQL(schema)
        .instrumentation(new MaxQueryDepthInstrumentation(10)) // Profundidad máxima 10
        .build();

3. Caché de consultas

El caché es tu amigo en aplicaciones con alta carga. Se puede cachear en varios niveles:

  • Cachear consultas GraphQL: guarda los resultados de ejecuciones a nivel de servidor.
  • Cache en la base de datos: usa un segundo nivel de caché (por ejemplo, en Hibernate) para reducir la carga en la base.
  • Caché del lado del cliente: usa herramientas como Apollo Client para cachear en el cliente.

Ejemplo de uso de Redis para cachear datos de GraphQL:


@Service
public class UserService {

    @Autowired
    private RedisTemplate<String, User> redisTemplate;

    public User getUserById(Long id) {
        User cachedUser = redisTemplate.opsForValue().get("user:" + id);
        if (cachedUser != null) {
            return cachedUser;
        }
        User user = userRepository.findById(id).orElseThrow(); // Obtenemos de la base de datos
        redisTemplate.opsForValue().set("user:" + id, user, 10, TimeUnit.MINUTES); // Guardamos en caché
        return user;
    }
}

4. Optimización del esquema

Asegúrate de que tu esquema GraphQL esté pensado y sea minimalista. Quita campos innecesarios, agrupa datos relacionados y aclara los parámetros de las consultas.

Ejemplo de esquema actualizado:

En lugar de:


type User {
  id: ID!
  name: String!
  posts: [Post]!
  comments: [Comment]!
}

Considera esta opción:


type User {
  id: ID!
  name: String!
  recentActivity: RecentActivity!
}

type RecentActivity {
  posts: [Post]!
  comments: [Comment]!
}

Esto ayuda a que los clientes pidan solo los datos necesarios y mejora la legibilidad del esquema.


Práctica: Optimización de consultas en un GraphQL API

Has recibido una consulta del cliente que se usa con frecuencia en tu aplicación:


query {
  users {
    id
    name
    posts {
      id
      title
    }
  }
}

Esta consulta provoca 1 consulta para los usuarios y N consultas por cada usuario para obtener los posts. Tu tarea:

  1. Usar DataLoader para eliminar el problema N+1.
  2. Añadir una limitación en la profundidad de las consultas.
  3. Cachear las respuestas para mejorar el rendimiento.

Implementación

  1. Configura DataLoader como se describió antes.
  2. Añade la limitación de profundidad de consultas.
  3. Cachea las consultas usando Redis:

@Bean
public CachingDataFetcher postCachingDataFetcher(PostService postService) {
    return new CachingDataFetcher(environment -> {
        Long userId = environment.getSource();
        return cacheManager.get("user_posts", userId.toString(), 
            () -> postService.getPostsByUserId(userId)
        );
    });
}

Conclusión del tema

Ahora sabes qué problemas pueden surgir con el rendimiento en GraphQL y cómo resolverlos. Has aprendido a usar DataLoader para hacer batching de consultas, a limitar la profundidad, a cachear datos y a optimizar el esquema. Estas técnicas te ayudarán a construir un GraphQL API más eficiente, estable y escalable, que gustará tanto a los clientes como a ti (y a tu base de datos).

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION