Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The JavaScript driver is about 4 times slower than the C# one #1223

Open
AFatNiBBa opened this issue Sep 11, 2024 · 4 comments
Open

The JavaScript driver is about 4 times slower than the C# one #1223

AFatNiBBa opened this issue Sep 11, 2024 · 4 comments
Labels

Comments

@AFatNiBBa
Copy link

AFatNiBBa commented Sep 11, 2024

Bug Report

The JavaScript driver is about 4 times slower than the C# one, but the query takes the same amount on the database side.
It's the fact alone that one is written in C# enough to justify this big increase in execution times?

My code

JavaScript

async function execute(pass: string) {
  const db = driver("bolt://localhost:7687", auth.basic("neo4j", pass));

  try
  {
    console.time("query");
    const cypher = "MATCH (master:Cont_SCH_Master)-[:HAS]->(x:Cont_SCH) RETURN master, collect(x) as details";
    const res = await db.executeQuery(cypher, undefined, { database: "neo4j", routing: "READ" });
    console.timeEnd("query");
    console.log(res.summary);
    return res.records;
  }
  finally { await db.close(); }
}

C#

static async Task<List<IRecord>> Execute(string pass)
{
  await using var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", pass));
  await using var session = driver.AsyncSession();

  var sw = Stopwatch.StartNew();
  try
  {
    var res = await session.RunAsync("MATCH (master:Cont_SCH_Master)-[:HAS]->(x:Cont_SCH) RETURN master, collect(x) as details");
    return await res.ToListAsync();
  }
  finally
  {
    Console.WriteLine($"query: {sw.Elapsed.TotalMilliseconds} ms");
  }
}

Timings

JavaScript
image

C#

query: 11670,4118 ms

My Environment

Javascript Runtime Version: Edge 128.0.2739.67
Driver Version: 5.24.1
Neo4j Version and Edition: 5.23.0 (enterprise)
Operating System: Windows 11

@AFatNiBBa AFatNiBBa added the bug label Sep 11, 2024
@AFatNiBBa
Copy link
Author

AFatNiBBa commented Sep 11, 2024

I also tried hacking together a custom client that uses the HTTP API and the same query takes up to ~11 seconds less

query: 30378.218994140625 ms
async function execute(pass: string) {
  const conn = new Connection("http://localhost:7474/db/neo4j/tx/commit", authBasic("neo4j", pass));
  console.time("query");
  const out = [];
  for await (const elm of conn.run`MATCH (master:Cont_SCH_Master)-[:HAS]->(x:Cont_SCH) RETURN master, collect(x) as details`)
    out.push(elm);
  console.timeEnd("query");
  return out;
}

The source code of the custom client

(If you want to test it for yourself)

import { JSONParser, ParsedElementInfo } from "@streamparser/json";

/** Oggetto che rappresenta una singola query Neo4j */
export type Statement = { statement: string, parameters?: object };

/** Oggetto che permette di evitare di ripetere l'autenticazione per ogni query */
export class Connection {
    constructor(public url: string, public auth: string) { }

	/**
	 * Crea uno {@link Statement} passando i parametri a {@link stmt} e lo esegue tramite {@link query}
	 * @param str La stringa contenente la query in Cypher
 	 * @param args I parametri della query
	 */
	run(str: TemplateStringsArray, ...args: any[]) {
		return this.query([ stmt(str, args) ]);
	}

	/**
	 * Esegue una lista di istruzioni
	 * @param list La lista di istruzioni da eseguire
	 */
	async *query(list: Statement[]) {
		const res = await fetch(this.url, createRequest(this.auth, list));
		const reader = res
			.body!
			.pipeThrough(new TextDecoderStream())
			.pipeThrough(createJSONParserStream(["$.results.*.data.*"]));
		
		for await (const elm of reader)
			yield elm.value;
	}
}

/**
 * Funzione che codifica le credenziali di una autenticazione basica
 * @param user L'utente col quale autenticarsi
 * @param pass La password di {@link user}
 */
export function authBasic(user: string, pass: string) {
    return `Basic ${btoa(`${user}:${pass}`)}`;
}

/**
 * Crea uno {@link Statement} usando una stringa template
 * @param str La stringa contenente la query in Cypher
 * @param args I parametri della query
 */
export function stmt(str: TemplateStringsArray, ...args: any[]): Statement {
	var statement = str[0];
	const parameters: Record<string, any> = {};
	for (var i = 0; i < args.length; i++) {
		const k = `v${i}`;
		parameters[k] = args[i];
		statement += `$${k}${str[i + 1]}`;
	}
	return { statement, parameters };
}

/**
 * Crea un {@link TransformStream} che parsa un pezzo per volta un flusso di stringhe
 * @param paths Percorsi JSON da emettere
 */
export function createJSONParserStream(paths?: string[]) {
	const parser = new JSONParser({ paths });
	return new TransformStream<string, ParsedElementInfo.ParsedElementInfo>({
		start: controller => parser.onValue = controller.enqueue.bind(controller),
		transform: chunk => parser.write(chunk)
	});
}

/**
 * Crea il boilerplate per eseguire una richiesta
 * @param auth L'header di autenticazione
 * @param statements La lista delle istruzioni da eseguire
 */
function createRequest(auth: string, statements: Statement[]): RequestInit {
    return {
        method: "post",
		body: JSON.stringify({ statements }),
		headers: {
			"Accept": "application/json;charset=UTF-8",
			"Content-Type": "application/json",
			"Authorization": auth
		}
    };
}

@MaxAake
Copy link
Contributor

MaxAake commented Sep 16, 2024

Hi, thanks for reaching out!

It's the fact alone that one is written in C# enough to justify this big increase in execution times?

Short answer: Yes

Long answer: The JS driver is slower when it comes to parsing large records, which I would guess is what is taking up the bulk of the time in your test. JSON.parse is going to be faster, but the driver parses from Bolt, which is a binary protocol, and applies special handling to some parts of responses.

@AFatNiBBa
Copy link
Author

Is there maybe some possibility to speed up the binary parsing by taking advantage of WebAssembly?

@MaxAake
Copy link
Contributor

MaxAake commented Oct 3, 2024

That's potentially quite a good suggestion. There's currently a number of more pressing features and changes to be implemented, but I will take some time, when available, to look into the feasibility and cost/benefit of that work.

Thank you for raising this, I'll get back to you when I have an update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants