nuwave / nuwave/lighthouse

`DirectiveLocator:associated()` performance

Open
#2,041 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

performance
Dominant language
PHP
Stars
3.5k
Forks
468
Avg merge
3h 9m
Merged PRs (30d)
2

Description

We intensively use directives and some of our queries return hundreds and thousands of objects. I've found that each call of DirectiveLocator::associated($node) create new instances of directives and this lead to (for 500 objects)

image

In PHP 8.0 it is possible to use WeakMap to cache directive instances and we will get:

image

<?php declare(strict_types = 1);

namespace App\GraphQL;

use App\GraphQL\Extensions\Lighthouse\DirectiveLocator;
use App\GraphQL\Extensions\Lighthouse\Directives\ValidatorDirective;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\ServiceProvider;
use Nuwave\Lighthouse\Schema\DirectiveLocator as LighthouseDirectiveLocator;
use Nuwave\Lighthouse\Validation\ValidatorDirective as LighthouseValidatorDirective;

class Provider extends ServiceProvider {
    // <editor-fold desc="Register">
    // =========================================================================
    public function register(): void {
        parent::register();

        $this->app->singleton(LighthouseDirectiveLocator::class, DirectiveLocator::class);
        $this->app->bind(LighthouseValidatorDirective::class, ValidatorDirective::class);

        $this->booting(static function (Dispatcher $dispatcher, LighthouseDirectiveLocator $locator): void {
            $dispatcher->subscribe($locator);
        });
    }
}
<?php declare(strict_types = 1);

namespace App\GraphQL\Extensions\Lighthouse;

use GraphQL\Language\AST\Node;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Collection;
use Nuwave\Lighthouse\Events\ManipulateAST;
use Nuwave\Lighthouse\Events\StartExecution;
use Nuwave\Lighthouse\Schema\DirectiveLocator as LighthouseDirectiveLocator;
use WeakMap;

/**
 * @see https://github.com/nuwave/lighthouse/issues/2041
 */
class DirectiveLocator extends LighthouseDirectiveLocator {
    /**
     * @var \WeakMap<\GraphQL\Language\AST\Node,\Illuminate\Support\Collection<\Nuwave\Lighthouse\Support\Contracts\Directive>
     */
    private WeakMap $directives;

    private bool $execution = false;

    public function __construct(Dispatcher $eventsDispatcher) {
        parent::__construct($eventsDispatcher);

        $this->reset();
    }

    public function subscribe(Dispatcher $dispatcher): void {
        $dispatcher->listen(ManipulateAST::class, function (): void {
            $this->reset();
        });
        $dispatcher->listen(StartExecution::class, function (): void {
            $this->execution = true;
        });
    }

    protected function reset(): void {
        $this->directives = new WeakMap();
        $this->execution  = false;
    }

    public function associated(Node $node): Collection {
        // While AST Manipulation phase the node/directive/args can be changed
        // so we must not cache anything.
        if (!$this->execution) {
            return parent::associated($node);
        }

        // While Execution phase nothing can be changed (?), so
        if (!isset($this->directives[$node])) {
            $this->directives[$node] = parent::associated($node);
        }

        return $this->directives[$node];
    }
}
<?php declare(strict_types = 1);

namespace App\GraphQL\Extensions\Lighthouse\Directives;

use Nuwave\Lighthouse\Execution\Arguments\ArgumentSet;
use Nuwave\Lighthouse\Validation\ValidatorDirective as LighthouseValidatorDirective;

class ValidatorDirective extends LighthouseValidatorDirective {
    /**
     * @inheritDoc
     */
    public function setArgumentValue($argument): self {
        $result = parent::setArgumentValue($argument);

        if ($argument instanceof ArgumentSet) {
            $this->validator()->setArgs($argument);
        }

        return $result;
    }
}

Unfortunately, I have no idea (and time) how to fix it for PHP < 8.0

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at DirectiveLocator::associated(Node) and trace the ManipulateAST and StartExecution event lifecycle that controls when directive instances may be cached. Compare the proposed WeakMap approach with the existing parent locator and PHP versions supported by the project; done means repeated execution-phase lookups reuse instances without changing AST-phase behavior or dropping older-version support.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, laravel, php
Domain
api, backend-api-design, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.