Skip to content

Fulfillment lines relation combined with lines relation on order causes quadratic database response growth #3459

@Dennor

Description

@Dennor

Small preface, I'm not sure if it's a bug as it mostly is a performance issue but it does not feel like feature either.

Describe the bug
Fetching an order from admin panel with fulfillment lines relation (default order view in admin panel due to Relations decorator) causes the database response from findOne to grow quadratically with the number of order lines.

For example. Given 20 lines in order, 2 channels, 2 prices in each channel, 2 translations for variant, 2 translations for shipping method and created fulfillment the query will return:

20 (number of order lines) * 2 (number of channels) * 2 (number of prices in each channel) * 2 (number of translations for variants) * 2 (number of translations for shipping method) * 20 (number of fulfillment lines) = 6400 rows

Instead of required 20 rows

Basically the number of rows returned by order queries (both findOne and findAll) with fulfillment lines and regular order lines can be summarized as:

n - Number of order lines

n^2 * count_of_variant_prices_for_given_variant * num_of_translations_for_given_variant * num_of_translations_for_shipping_method

To Reproduce
Steps to reproduce the behavior:

  1. Create an order with 20 lines
  2. Go to order view.
  3. Create fulfillment.
  4. Go to order view again.
  5. Log out the OrderService.findOne query and run it against database directly.

Expected behavior
No overfetch.

Environment (please complete the following information):

  • @vendure/core version: 2.3 (but I believe it also aplies to 3+ as there was no change to that particular logic here)
  • 22.14.0
  • Database (mysql/postgres etc): postgresql in my case, but probably applies to all?

Additional context
An ugly snippet like this:

    private restrictOrderJoins(ctx: RequestContext, qb: {
        expressionMap: QueryExpressionMap,
        getParameters: () => Record<string, unknown>,
        setParameter: (key: string, value: unknown) => unknown,
    }): void {
        if (!qb.getParameters()['languageCode']) {
            qb.setParameter('languageCode', ctx.languageCode)
        }

        // fulfillments.lines will cause the quadratic growth of response if queried along with
        // lines relation without additional restrictions. So we intentially modify that relation.
        // Additionally prevent overfetching of unnecessary translations and prices.
        qb.expressionMap.joinAttributes.forEach((join) => {
            if (!join.condition) {
                const inverseTarget = join.relationCache?.inverseEntityMetadata.target;
                if (inverseTarget) {
                    if (!join.condition && [
                            ProductVariantTranslation,
                            ShippingMethodTranslation,
                            PromotionTranslation,
                        ].find((el) => el === inverseTarget)
                    ) {
                        join.condition = `"${join.alias.name}"."languageCode" = :languageCode`
                    }
                }
                if (inverseTarget === ProductVariantPrice) {
                    join.condition = `"${join.alias.name}"."currencyCode" = "order"."currencyCode" AND "${join.alias.name}"."channelId" = :channelId`
                }
                if (inverseTarget === FulfillmentLine) {
                    const lines = qb.expressionMap.joinAttributes.find((join) => join.relationCache?.inverseEntityMetadata.target === OrderLine);
                    if (lines) {
                        join.condition = `"${join.alias.name}"."orderLineId" = "${lines.alias.name}"."id"`;
                    }
                }
            }
        });
    }

did make the query return expected number of rows.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    📅 Planned

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions