Load/stress 使用 Hasura Cloud Graphql 作为后端和订阅在 SPA 中进行测试

Load/stress test in a SPA with Hasura Cloud Graphql as a backend and subscriptions

我正在尝试对

进行性能测试

问题是我不知道该怎么做,或者我是否做对了,看看我们的大部分东西是如何 "subscriptions/mutations" 我不得不t运行形成查询。我试着用 k6 和 Jmeter 做这些测试,但我不确定我是否做对了。

k6 测试

起初,我快速搜索并收集了大约 10 个常用的订阅。然后我尝试使用 k6 https://k6.io/docs/using-k6/http-requests/ 创建一个性能测试,但我无法创建一个有效的订阅测试,所以我只是 t运行 将每个订阅转换为一个查询并执行 http.post使用此设置:

export const options = {
  stages: [
    { duration: '30s', target: 75 },
    { duration: '120s', target: 75 },
    { duration: '60s', target: 50 },
    { duration: '30s', target: 30 },
    { duration: '10s', target: 0 }
  ]
};

export default function () {
  var res = http.post(prod,  
    JSON.stringify({
    query: listaQueries.GetDesafiosCursosByKey(
      keys.desafioCursoKey
    )}), params);
  sleep(1)
}

我对每个查询都这样做,运行对每个测试都单独这样做。不幸的是,我得到的数字很糟糕,不知何故我们的测试环境比生产环境好很多。 (唯一的区别 afaik 是我们使用 Hasura Cloud 进行生产)。

我试图实现 websocket,但我无法让它们工作并配置它们来进行 stress/load 测试。

K6 result

Jmeter测试

在那之后,我尝试了一些与 Jmeter 类似的东西,但我还是不知道如何设置订阅测试(一段时间后,我在博客中读到 jmeter 不支持它 https://qainsights.com/deep-dive-into-graphql-in-jmeter/ ) 所以我简单地 t运行 将所有订阅变成一个查询并尝试做同样的事情,但是我得到的数字是不同的并且比 k6 高得多。

Jmeter query Config 1

Jmeter query config 2

Jmeter thread config

问题

我不确定我做的是否正确,如果 t运行sforming every subscription into a query and perform a http request is a correct approach for it. (至少我知道那些查询 return 数据是正确的)。

我是否应该只增加 VUS/threads 的数量,直到我得到一个恒定的超时来模拟压力测试?有一些测试导致网站 Graphql error 出现 graphql 错误,而其他测试则出现

""WARN[0059] Request Failed error="Post \"https://xxxxxxx-xxxxx.herokuapp.com/v1/graphql\": EOF""

在 k6 控制台中。 或者我应该放弃 k6/jmeter 并尝试寻找另一种工具来执行这些测试?

提前谢谢你,抱歉我的英语和解释,但我在这方面完全是个新手。

I'm not sure if i'm doing it correctly, if transforming every subscription into a query and perform a http request is a correct approach for it. (At least I know that those queries return the data correctly).

理想情况下,您将使用 WebSocket,因为这是实际客户最有可能使用的。

有关代码示例,请查看答案 here

这是一个更完整的示例,它使用 main.js 入口脚本和 subscriptions\bikes.brands.js 中的模块化订阅代码。它还使用 Httpx 库来设置全局请求 header:

// main.js
import { Httpx } from 'https://jslib.k6.io/httpx/0.0.5/index.js';

import { getBikeBrandsByIdSub } from './subscriptions/bikes-brands.js';

const session = new Httpx({
  baseURL: `http://54.227.75.222:8080`
});

const wsUri = 'wss://54.227.75.222:8080/v1/graphql';

const pauseMin = 2;
const pauseMax = 6;

export const options = {};

export default function () {
  session.addHeader('Content-Type', 'application/json');

  getBikeBrandsByIdSub(1);
}
// subscriptions/bikes-brands.js
import ws from 'k6/ws';

/* using string concatenation */
export function getBikeBrandsByIdSub(id) {
  const query = `
    subscription getBikeBrandsByIdSub {
      bikes_brands(where: {id: {_eq: ${id}}}) {
        id
        brand
        notes
        updated_at
        created_at
      }
    }
  `;

  const subscribePayload = {
    id: "1",
    payload: {
      extensions: {},
      operationName: "query",
      query: query,
      variables: {},
    },
    type: "start",
  }

  const initPayload = {
    payload: {
      headers: {
        "content-type": "application/json",
      },
      lazy: true,
  
    },
    type: "connection_init",
  };

  console.debug(JSON.stringify(subscribePayload));

  // start a WS connection
  const res = ws.connect(wsUri, initPayload, function(socket) {
    socket.on('open', function() {
      console.debug('WS connection established!');

      // send the connection_init:
      socket.send(JSON.stringify(initPayload));

      // send the chat subscription:
      socket.send(JSON.stringify(subscribePayload));
    });

    socket.on('message', function(message) {
      let messageObj;
      try {
        messageObj = JSON.parse(message);
      }
      catch (err) {
        console.warn('Unable to parse WS message as JSON: ' + message);
      }

      if (messageObj.type === 'data') {
        console.log(`${messageObj.type} message received by VU ${__VU}: ${Object.keys(messageObj.payload.data)[0]}`);
      }

      console.log(`WS message received by VU ${__VU}:\n` + message);
    });
  });
}

Should i just increase the number of VUS/threads until i get a constant timeout to simulate a stress test?

仅在负载下发生的超时和错误是您可能在某处遇到瓶颈的信号。你只看到负载下的 EOF 吗?这些基本上是服务器提前发回不完整的 responses/closing 连接,这在正常情况下不应该发生。

我的期望是您的测试应该尽可能地复制真实用户 activity。我怀疑真实用户是否会直接向 GraphQL 发送请求,并且 well-behaved 负载测试必须尽可能地复制真实应用程序的使用情况。

所以我认为您应该进入 HTTP protocol 级别并模仿真实浏览器的网络足迹,而不是尝试提出单独的 GraphQL 查询。

关于 JMeter 和 k6 的差异,在相同的硬件和 运行 以最大速度请求的情况下,k6 可能会产生更高的吞吐量,正如 Open Source Load Testing Tools 2021 article, however given you're trying to simulate real users using real browsers accessing your applications and the real users don't hammer the application non-stop, they need some time to "think" between operations you should be getting the same number of requests for both load testing tools, if JMeter doesn't give you the load you want to conduct make sure to follow JMeter Best Practices and/or consider running it in distributed mode 中的基准测试所证明的那样.