Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions .github/workflows/graphql-benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: GraphQL Benchmark Comparison

on: [push, pull_request]

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
graphql-benchmark:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install k6
run: |
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

- name: Install Docker Compose
run: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

- name: Start PostgreSQL + Hasura stack
run: |
docker-compose -f docker-compose.postgresql-hasura.yml up -d
echo "Waiting for Hasura to be ready..."
timeout 120s bash -c 'until curl -f http://localhost:8080/healthz; do sleep 2; done'

- name: Apply Hasura metadata
run: |
# Install Hasura CLI
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
export PATH=$PATH:$HOME/.hasura/bin
# Apply metadata
cd postgresql-hasura
hasura metadata apply --endpoint http://localhost:8080

- name: Build and start Doublets GraphQL stack
run: |
docker-compose -f docker-compose.doublets-gql.yml up -d --build
echo "Waiting for Doublets GraphQL to be ready..."
timeout 180s bash -c 'until curl -f http://localhost:60341/v1/graphql; do sleep 5; done'

- name: Run Hasura GraphQL benchmarks
run: |
cd benchmarks/k6
k6 run --out json=hasura-results.json hasura-benchmark.js
continue-on-error: true

- name: Run Doublets GraphQL benchmarks
run: |
cd benchmarks/k6
k6 run --out json=doublets-results.json doublets-benchmark.js
continue-on-error: true

- name: Process benchmark results
run: |
cd benchmarks
pip install matplotlib numpy
python process-results.py k6/hasura-results.json k6/doublets-results.json | tee results.txt

- name: Publish benchmark results to gh-pages
run: |
git config --global user.email "linksplatform@gmail.com"
git config --global user.name "LinksPlatformBencher"
cd benchmarks
git fetch
git checkout gh-pages || git checkout --orphan gh-pages
mkdir -p Docs
mv -f bench_graphql.png Docs/
mv -f bench_graphql_log_scale.png Docs/
mv -f results.txt Docs/graphql-results.txt
git add Docs/
git commit -m "Publish GraphQL benchmark results" || echo "No changes to commit"
git push origin gh-pages || echo "No changes to push"

- name: Save benchmark artifacts
uses: actions/upload-artifact@v4
with:
name: GraphQL benchmark results
path: |
benchmarks/bench_graphql.png
benchmarks/bench_graphql_log_scale.png
benchmarks/results.txt
benchmarks/k6/hasura-results.json
benchmarks/k6/doublets-results.json

- name: Stop services
if: always()
run: |
docker-compose -f docker-compose.postgresql-hasura.yml down
docker-compose -f docker-compose.doublets-gql.yml down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ The results below represent the amount of time (ns) the operation takes per iter
![Image of Rust benchmark (pixel scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/gh-pages/Docs/bench_rust.png?raw=true)
![Image of Rust benchmark (log scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/gh-pages/Docs/bench_rust_log_scale.png?raw=true)

### GraphQL
![Image of GraphQL benchmark (pixel scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/gh-pages/Docs/bench_graphql.png?raw=true)
![Image of GraphQL benchmark (log scale)](https://github.com/linksplatform/Comparisons.PostgreSQLVSDoublets/blob/gh-pages/Docs/bench_graphql_log_scale.png?raw=true)

### Raw benchmark results (all numbers are in nanoseconds)

| Operation | Doublets United Volatile | Doublets United NonVolatile | Doublets Split Volatile | Doublets Split NonVolatile | PSQL NonTransaction | PSQL Transaction |
Expand Down
137 changes: 137 additions & 0 deletions benchmarks/k6/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Common utilities for GraphQL benchmarking with k6
import http from 'k6/http';
import { check } from 'k6';

export class GraphQLBenchmark {
constructor(endpoint, headers = {}) {
this.endpoint = endpoint;
this.headers = {
'Content-Type': 'application/json',
...headers
};
}

query(query, variables = {}) {
const payload = JSON.stringify({
query: query,
variables: variables
});

const response = http.post(this.endpoint, payload, { headers: this.headers });

check(response, {
'GraphQL request successful': (r) => r.status === 200,
'No GraphQL errors': (r) => {
const body = JSON.parse(r.body);
return !body.errors;
}
});

return response;
}
}

// GraphQL queries and mutations
export const queries = {
// Create operations
createPointLink: `
mutation CreatePointLink {
createPointLink {
id
source
target
}
}
`,

createLink: `
mutation CreateLink($source: ID!, $target: ID!) {
createLink(input: { source: $source, target: $target }) {
id
source
target
}
}
`,

// Update operation
updateLink: `
mutation UpdateLink($id: ID!, $source: ID, $target: ID) {
updateLink(input: { id: $id, source: $source, target: $target }) {
id
source
target
}
}
`,

// Delete operation
deleteLink: `
mutation DeleteLink($id: ID!) {
deleteLink(id: $id)
}
`,

// Read operations - Each variants
allLinks: `
query AllLinks($limit: Int, $offset: Int) {
allLinks(limit: $limit, offset: $offset) {
id
source
target
}
}
`,

linkById: `
query LinkById($id: ID!) {
linkById(id: $id) {
id
source
target
}
}
`,

concreteLinks: `
query ConcreteLinks($source: ID!, $target: ID!, $limit: Int, $offset: Int) {
concreteLinks(source: $source, target: $target, limit: $limit, offset: $offset) {
id
source
target
}
}
`,

outgoingLinks: `
query OutgoingLinks($source: ID!, $limit: Int, $offset: Int) {
outgoingLinks(source: $source, limit: $limit, offset: $offset) {
id
source
target
}
}
`,

incomingLinks: `
query IncomingLinks($target: ID!, $limit: Int, $offset: Int) {
incomingLinks(target: $target, limit: $limit, offset: $offset) {
id
source
target
}
}
`
};

// Generate random variables for testing
export function randomVariables() {
const id = Math.floor(Math.random() * 1000) + 1;
return {
id: id.toString(),
source: (Math.floor(Math.random() * 1000) + 1).toString(),
target: (Math.floor(Math.random() * 1000) + 1).toString(),
limit: 10,
offset: 0
};
}
119 changes: 119 additions & 0 deletions benchmarks/k6/doublets-benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// k6 benchmark script for Doublets GraphQL
import { GraphQLBenchmark, queries, randomVariables } from './common.js';
import { group, sleep } from 'k6';

export let options = {
scenarios: {
create_load: {
executor: 'constant-arrival-rate',
rate: 100, // 100 requests per second
timeUnit: '1s',
duration: '30s',
preAllocatedVUs: 10,
maxVUs: 50,
exec: 'createScenario',
},
update_load: {
executor: 'constant-arrival-rate',
rate: 100,
timeUnit: '1s',
duration: '30s',
preAllocatedVUs: 10,
maxVUs: 50,
exec: 'updateScenario',
startTime: '35s',
},
delete_load: {
executor: 'constant-arrival-rate',
rate: 100,
timeUnit: '1s',
duration: '30s',
preAllocatedVUs: 10,
maxVUs: 50,
exec: 'deleteScenario',
startTime: '70s',
},
read_load: {
executor: 'constant-arrival-rate',
rate: 200,
timeUnit: '1s',
duration: '60s',
preAllocatedVUs: 20,
maxVUs: 100,
exec: 'readScenario',
startTime: '105s',
}
},
thresholds: {
http_req_duration: ['p(95)<1000'], // 95% of requests should be below 1s
http_req_failed: ['rate<0.1'], // Error rate should be below 10%
},
};

const doublets = new GraphQLBenchmark('http://localhost:60341/v1/graphql');

export function createScenario() {
group('Create Operations', function() {
// Create point link
doublets.query(queries.createPointLink);

// Create regular link
const vars = randomVariables();
doublets.query(queries.createLink, {
source: vars.source,
target: vars.target
});
});
}

export function updateScenario() {
group('Update Operations', function() {
const vars = randomVariables();
doublets.query(queries.updateLink, {
id: vars.id,
source: vars.source,
target: vars.target
});
});
}

export function deleteScenario() {
group('Delete Operations', function() {
const vars = randomVariables();
doublets.query(queries.deleteLink, { id: vars.id });
});
}

export function readScenario() {
group('Read Operations', function() {
const vars = randomVariables();

// Each All
doublets.query(queries.allLinks, { limit: 10, offset: 0 });

// Each Identity
doublets.query(queries.linkById, { id: vars.id });

// Each Concrete
doublets.query(queries.concreteLinks, {
source: vars.source,
target: vars.target,
limit: 10,
offset: 0
});

// Each Outgoing
doublets.query(queries.outgoingLinks, {
source: vars.source,
limit: 10,
offset: 0
});

// Each Incoming
doublets.query(queries.incomingLinks, {
target: vars.target,
limit: 10,
offset: 0
});
});
}
Loading
Loading