Graphql Benchmark with Locust

Locust is a load testing framework designed to define user behaviors. More about locust can be found here.

Test Suite Setup

The test is performed from this commit id.

The test repository is available here.

Data for restaurants was used for testing purposes. Here is the approximate data size initially:
Restaurant: 8422
Dishes: 4158700
Cuisine: 116

The load test was done on a single server setup of dgraph with locust running for 100 users at a spawn rate of 10 users per sec. We can simulate the load in a distributed manner too.

For simulating queries, initial data was built with 8422 restaurants, 116 cuisines, and 10K dishes. After that random combinations are used to perform all the queries.

The total simulation time is 30 minutes. Memory, CPU, and goroutines have been profiled in a period of 30 seconds. The complete profiles list can be found here.

The tests were run after performing a set of random queries to initialize the random state of the server.

During testing, I didn’t get any major memory spikes or CPU spikes during the simulation.

Simulation Without Auth

Schema can be found here.
Queries and mutations are simulated separately.

Query Simulation

Queries Used for searching restaurants:

query($name: String) {
  queryRestaurant(
    filter: {
      name: {
        allofterms: $name
      }
    }) {
    id
    name
    cuisines(first: 100) {
      name
      id
      dishes(first: 100) {
        id 
        name
      }
    }
  }
}

Queries used for Searching dishes

query ($name: String, $dishName: String, $restName: String) {
  queryDish(
    filter: {
      name: {
        allofterms: $dishName
      }
    }
  ) {
    id 
    name
    cuisine(filter:{
      name: { eq: $name }
    }) {
      name
      restaurants(filter: {
        name: { allofterms: $restName }
      }) {
        id
        name
      }
    }
  }
}

Queries to search cuisine

query($name: String, $dishName: String, $restName: String) {
  queryCuisine(
    filter: {
      name: {
        eq:$name
      }
    }
  ) {
    id
    name
    restaurants(filter:{
      name: {
        allofterms: $restName
      }
    }) {
      name
    }
    dishes (
      filter: {
        name: {
          allofterms: $dishName
        }
      },
      first: 100) {
      name
    }
  }
}

Results

Observations

Cpu Profile

Graph

Memory Profile

Graph

Mutation simulation

mutation to add restaurant

mutation(
  $lat: Float!
  $long: Float!
  $address: String!
  $locality: String!
  $cityId: String!
  $countryId: String!
  $countryname: String!
  $restName: String!
  $rating: Float!
  $costFor2: Float!
  $currency: String!
  $cuisineName: String!
  
  $dishName: String!
  $dishPic: String!
  $dishPrice: Float!
  $description: String!
  $isVeg: Boolean!
  $zipcode: Int!
  
) {
  addRestaurant(
    input: [
      {
        dishes: [{
          name: $dishName
          pic: $dishPic
          price: $dishPrice
          description: $description
          isVeg: $isVeg
          
        }]
        addr: {
          lat: $lat
          long: $long
          address: $address
          locality: $locality
          city: {
          	id: $cityId
            name: $cityId
            country: {
              id: $countryId
              name: $countryname
            }
          }
          zipcode: $zipcode
          
        }
        name: $restName
        xid: $restName
        createdAt: 1234567890
        rating: $rating
        costFor2: $costFor2
        currency: $currency
        cuisines: [{
          name: $cuisineName
        }]
      }
    ]
  ) {
    restaurant {
      id
    }
  }
}

mutation to add dish

mutation($dishName: String!, $price: Float!, $isVeg: Boolean!, $restId: ID!) {
  addDish(
    input: [
      {
        name: $dishName
        price: $price
        isVeg: $isVeg
        servedBy: {
          id: $restId
        }
      }
    ]
  ) {
    dish {
      id
    }
  }
}

mutation to add cuisine

mutation($cuisineName: String!) {
  addCuisine(
    input: [
      {
        name: $cuisineName
      }
    ]
  ) {
    cuisine {
      id
    }
  }
}

Results

Observations

Cpu Profile

Graph

Memory Profile

Graph

Simulation with Auth

Schema can be found here. Queries and mutations are simulated separately.

Query Simulation

Six queries are used. 3 with admin role are same as the one mentioned in without auth and 3 with restaurant scope.

Queries with restaurant scope

Query to search restaurant

query {
 queryRestaurant{
   id
   name
   cuisines(first: 10) {
     name
     id
   }
 }
}

Query to search Dish

query ($name: String, $dishName: String) {
  queryDish(
    filter: {
      name: {
        allofterms: $dishName
      }
    }
  ) {
    id 
    name
    cuisine(filter:{
      name: { eq: $name }
    }) {
      name
      restaurants {
        id
        name
      }
    }
  }
}

Query to search Cuisine

query($name: String, $dishName: String) {
  queryCuisine(
    filter: {
      name: {
        eq:$name
      }
    }
  ) {
    id
    name
    restaurants {
      id
      name
    }
    dishes (
      filter: {
        name: {
          allofterms: $dishName
        }
      },
      first: 100) {
      name
    }
  }
}

Results

Observations

Cpu Profile

Graph

Memory Profile

Graph

Mutations with Auth

mutations are performed with the admin role. mutations queries are the same as the one used without the auth scope.

Results

Observations

Cpu Profile

Graph

Memory Profile

Graph

@pawan @abhimanyusinghgaur let’s review this and see if we can add more test cases to this.

3 Likes

How often do we plan to run this? How do we plan to use this on a ongoing basis?

Nothing is planned as of yet. But one suggestion is we could make it part of release testing. Though would require few changes to automate it.

@pawan do we have anything planned to make it part of our regular testing?

Test Scenario: Trends with different number of users

For this simulation number of users has been varied from 10, 20, 50 and 100. Results and memory/cpu profiling has been added below. It can observed from the results that there is no unexpected surge in response time or memory/cpu usage.

Number of Users = 10

Query Simulation without Auth

Results:

Observations:

CPU Profile


[Graph]

Memory Profile


[Graph]

Number of Users = 20

Query Simulation without Auth

Results

Observations

CPU Profile


[Graph]

Memory Profile


[Graph]

Number of Users = 50

Query Simulation without Auth

Results

CPU Profile


[Graph]

Memory Profile


[Graph]

I talked to @pawan , and we believe that it should be part of DQL layer first. We would like to monitor the latency patterns and then extend it to the GraphQL layer.

@vvbalaji how should we proceed with this?