Skip to content

⚡ Make database file reading non-blocking#50

Closed
zknpr wants to merge 1 commit intomainfrom
jules-perf-async-file-read-8887365914515574585
Closed

⚡ Make database file reading non-blocking#50
zknpr wants to merge 1 commit intomainfrom
jules-perf-async-file-read-8887365914515574585

Conversation

@zknpr
Copy link
Copy Markdown
Owner

@zknpr zknpr commented Feb 7, 2026

This PR addresses a performance issue where reading large database files synchronously in the worker thread was blocking the event loop.

Changes:

  • Replaced synchronous fs.statSync and fs.readFileSync with their asynchronous counterparts from fs.promises in createDatabaseEngine.
  • Added a benchmark script tests/performance/benchmark.ts that creates a large dummy file and measures the time taken to read it, while also counting event loop ticks to detect blocking.

Results:

  • Baseline (Sync): ~8500ms duration, 8 event loop ticks (BLOCKED).
  • Optimized (Async): ~8376ms duration, ~3881 event loop ticks (NON-BLOCKING).

The change ensures that the extension remains responsive even when loading large database files.


PR created automatically by Jules for task 8887365914515574585 started by @zknpr

- Replaced `fs.readFileSync` with `fs.promises.readFile` in `src/core/sqlite-db.ts`.
- Replaced `fs.statSync` with `fs.promises.stat`.
- Added `tests/performance/benchmark.ts` to measure event loop blocking.
- Verified performance: Event loop ticks increased from ~8 to ~3881 during a 500MB file read, confirming non-blocking behavior.

Co-authored-by: zknpr <96851588+zknpr@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sq-lite-explorer Ready Ready Preview, Comment Feb 7, 2026 1:56pm

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @zknpr, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical performance bottleneck by converting synchronous database file reading operations to asynchronous ones. The change ensures that the application remains responsive and the event loop is not blocked, even when handling large database files. A new performance benchmark script has been added to validate this improvement, confirming that the asynchronous approach successfully prevents event loop blocking.

Highlights

  • Non-blocking Database File Reading: Synchronous file system operations (fs.statSync, fs.readFileSync) in createDatabaseEngine have been replaced with their asynchronous promise-based counterparts (fs.promises.stat, fs.promises.readFile) to prevent blocking the event loop when reading large database files.
  • Performance Benchmark Added: A new benchmark script (tests/performance/benchmark.ts) has been introduced to measure the performance impact of file reading. It creates a large dummy file, measures the time taken to process it, and monitors event loop ticks to detect blocking behavior.
  • Confirmed Performance Improvement: Benchmark results demonstrate a significant improvement in event loop responsiveness: the synchronous approach blocked the event loop (8 ticks over ~8500ms), while the asynchronous approach allowed for many more ticks (~3881 ticks over ~8376ms), indicating non-blocking behavior.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/core/sqlite-db.ts
    • Replaced fs.statSync with await fs.promises.stat for asynchronous file status retrieval.
    • Replaced fs.readFileSync with await fs.promises.readFile for asynchronous file content reading.
  • tests/performance/benchmark.ts
    • Added a new performance benchmark script.
    • Includes functions to create and delete a large (500MB) dummy test file.
    • Measures the execution time of createDatabaseEngine.
    • Monitors event loop ticks during the operation to detect blocking.
    • Prints results indicating whether the operation was blocking or non-blocking.
Activity
  • No human activity (comments, reviews) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses a performance issue by replacing synchronous file I/O with asynchronous methods, preventing the event loop from being blocked when reading large database files. The addition of a benchmark script is a great way to validate the improvement. My review includes a suggestion to enhance the benchmark script by making its file operations fully asynchronous and ensuring robust cleanup, which aligns with the overall goal of the PR.

Comment on lines +10 to +62
function createLargeFile() {
const buffer = Buffer.alloc(FILE_SIZE);
fs.writeFileSync(TEST_FILE, buffer);
}

function deleteLargeFile() {
if (fs.existsSync(TEST_FILE)) {
fs.unlinkSync(TEST_FILE);
}
}

async function runBenchmark() {
deleteLargeFile();
console.log('Creating large file...');
createLargeFile();
console.log(`Created ${FILE_SIZE / 1024 / 1024}MB test file.`);

const start = performance.now();

// Start a timer to check event loop blocking
let ticks = 0;
const interval = setInterval(() => {
ticks++;
}, 1); // 1ms interval

try {
console.log('Starting createDatabaseEngine...');
await createDatabaseEngine({
content: null,
filePath: TEST_FILE,
maxSize: 0,
});
} catch (e: any) {
// We expect an error because the file is not a valid SQLite database
// console.log('Expected error (invalid db):', e.message);
}

clearInterval(interval);
const end = performance.now();
const duration = end - start;

console.log(`Time taken: ${duration.toFixed(2)}ms`);
console.log(`Event loop ticks: ${ticks}`);

// If ticks are very low relative to duration, it means blocking occurred.
if (duration > 100 && ticks < (duration / 10)) {
console.log("RESULT: BLOCKED (Ticks significantly lower than duration)");
} else {
console.log("RESULT: NON-BLOCKING (or fast enough)");
}

deleteLargeFile();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The benchmark script can be improved in two ways:

  1. Use asynchronous file operations: The createLargeFile and deleteLargeFile functions currently use synchronous methods (writeFileSync, existsSync, unlinkSync). While this is for a test script, using their asynchronous counterparts from fs.promises would be more consistent with the non-blocking approach this PR promotes.

  2. Ensure robust cleanup: The runBenchmark function should use a try...finally block to guarantee that deleteLargeFile() is called, even if an unexpected error occurs during the benchmark. This prevents leaving large test files on disk.

The suggested code below implements these improvements.

async function createLargeFile() {
    const buffer = Buffer.alloc(FILE_SIZE);
    await fs.promises.writeFile(TEST_FILE, buffer);
}

async function deleteLargeFile() {
    try {
        await fs.promises.unlink(TEST_FILE);
    } catch (error: any) {
        if (error.code !== 'ENOENT') {
            throw error;
        }
    }
}

async function runBenchmark() {
    // Ensure cleanup from any previous runs.
    await deleteLargeFile();
    console.log('Creating large file...');
    await createLargeFile();
    console.log(`Created ${FILE_SIZE / 1024 / 1024}MB test file.`);

    try {
        const start = performance.now();

        // Start a timer to check event loop blocking
        let ticks = 0;
        const interval = setInterval(() => {
            ticks++;
        }, 1); // 1ms interval

        try {
            console.log('Starting createDatabaseEngine...');
            await createDatabaseEngine({
                content: null,
                filePath: TEST_FILE,
                maxSize: 0,
            });
        } catch (e: any) {
            // We expect an error because the file is not a valid SQLite database
            // console.log('Expected error (invalid db):', e.message);
        }

        clearInterval(interval);
        const end = performance.now();
        const duration = end - start;

        console.log(`Time taken: ${duration.toFixed(2)}ms`);
        console.log(`Event loop ticks: ${ticks}`);

        // If ticks are very low relative to duration, it means blocking occurred.
        if (duration > 100 && ticks < (duration / 10)) {
            console.log("RESULT: BLOCKED (Ticks significantly lower than duration)");
        } else {
            console.log("RESULT: NON-BLOCKING (or fast enough)");
        }
    } finally {
        await deleteLargeFile();
    }
}

@zknpr
Copy link
Copy Markdown
Owner Author

zknpr commented Feb 7, 2026

Merged as part of v1.3.0 release in PR #65

@zknpr zknpr closed this Feb 7, 2026
@google-labs-jules
Copy link
Copy Markdown
Contributor

Merged as part of v1.3.0 release in PR #65

Acknowledged. Thank you for the update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant