fix: use parallel relation fetching (#173)

optimizes performance of queries with many left joins
This commit is contained in:
Sebastian Rindom
2021-02-17 10:40:48 +01:00
committed by GitHub
parent 0ed6b4a6a0
commit 46006e4b06
6 changed files with 1192 additions and 2022 deletions

View File

@@ -6,6 +6,7 @@ class MockRepo {
softRemove,
find,
findOne,
findOneWithRelations,
findOneOrFail,
save,
findAndCount,
@@ -19,6 +20,7 @@ class MockRepo {
this.findOneOrFail_ = findOneOrFail;
this.save_ = save;
this.findAndCount_ = findAndCount;
this.findOneWithRelations_ = findOneWithRelations;
}
setFindOne(fn) {
@@ -53,6 +55,11 @@ class MockRepo {
return this.findOneOrFail_(...args);
}
});
findOneWithRelations = jest.fn().mockImplementation((...args) => {
if (this.findOneWithRelations_) {
return this.findOneWithRelations_(...args);
}
});
findOne = jest.fn().mockImplementation((...args) => {
if (this.findOne_) {
return this.findOne_(...args);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,56 @@
import { EntityRepository, Repository } from "typeorm"
import { flatten, groupBy, map, merge } from "lodash"
import { EntityRepository, Repository, FindManyOptions } from "typeorm"
import { Order } from "../models/order"
@EntityRepository(Order)
export class OrderRepository extends Repository<Order> {}
export class OrderRepository extends Repository<Order> {
public async findWithRelations(
relations: Array<keyof Order> = [],
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
): Promise<Order[]> {
const entities = await this.find(optionsWithoutRelations)
const entitiesIds = entities.map(({ id }) => id)
const entitiesIdsWithRelations = await Promise.all(
relations.map(relation => {
const relationParts = relation.split(".") as string[]
const partialRelations = relationParts.reduce(
(acc: string[], _: string, index: number) => {
const toPush = []
for (let i = 0; i <= index; i++) {
toPush.push(relationParts[i])
}
acc.push(toPush.join("."))
return acc
},
[] as string[]
)
return this.findByIds(entitiesIds, {
select: ["id"],
relations: partialRelations,
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entitiesAndRelationsById, entityAndRelations =>
merge({}, ...entityAndRelations)
)
}
public async findOneWithRelations(
relations: Array<keyof Order> = [],
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
): Promise<Order> {
const result = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}

View File

@@ -396,7 +396,7 @@ describe("OrderService", () => {
describe("retrieve", () => {
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: q => {
return Promise.resolve({})
},
})
@@ -412,8 +412,8 @@ describe("OrderService", () => {
it("calls order model functions", async () => {
await orderService.retrieve(IdMap.getId("test-order"))
expect(orderRepo.findOne).toHaveBeenCalledTimes(1)
expect(orderRepo.findOne).toHaveBeenCalledWith({
expect(orderRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
expect(orderRepo.findOneWithRelations).toHaveBeenCalledWith(undefined, {
where: { id: IdMap.getId("test-order") },
})
})
@@ -446,7 +446,7 @@ describe("OrderService", () => {
describe("update", () => {
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
case IdMap.getId("fulfilled-order"):
return Promise.resolve({
@@ -527,7 +527,7 @@ describe("OrderService", () => {
describe("cancel", () => {
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
case IdMap.getId("paid-order"):
return Promise.resolve({
@@ -606,7 +606,7 @@ describe("OrderService", () => {
describe("capturePayment", () => {
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
case IdMap.getId("fail"):
return Promise.resolve({
@@ -718,7 +718,7 @@ describe("OrderService", () => {
}
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
case "partial":
return Promise.resolve(partialOrder)
@@ -870,7 +870,7 @@ describe("OrderService", () => {
payments: [{ id: "payment_test" }],
}
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
default:
return Promise.resolve(order)
@@ -1043,7 +1043,7 @@ describe("OrderService", () => {
payments: [{ id: "payment_test" }],
}
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
default:
return Promise.resolve(order)
@@ -1129,7 +1129,7 @@ describe("OrderService", () => {
}
const orderRepo = MockRepository({
findOne: q => {
findOneWithRelations: (rel, q) => {
switch (q.where.id) {
case IdMap.getId("partial"):
return Promise.resolve(partialOrder)
@@ -1206,9 +1206,7 @@ describe("OrderService", () => {
jest.clearAllMocks()
})
const orderRepo = MockRepository({
findOne: jest
.fn()
.mockReturnValue(Promise.resolve({ id: IdMap.getId("order") })),
findOneWithRelations: () => Promise.resolve({ id: IdMap.getId("order") }),
})
it("fails if order/swap relationship not satisfied", async () => {
@@ -1268,7 +1266,7 @@ describe("OrderService", () => {
it("registers a swap as received", async () => {
const orderRepo = MockRepository({
findOne: jest.fn().mockReturnValue(
findOneWithRelations: () =>
Promise.resolve({
id: IdMap.getId("order_123"),
items: [
@@ -1278,8 +1276,7 @@ describe("OrderService", () => {
quantity: 1,
},
],
})
),
}),
})
const swapService = {
@@ -1329,7 +1326,7 @@ describe("OrderService", () => {
})
const orderRepo = MockRepository({
findOne: jest.fn().mockImplementation(q => {
findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cannot")) {
return Promise.resolve({
id: IdMap.getId("order"),
@@ -1353,7 +1350,7 @@ describe("OrderService", () => {
total: 100,
refunded_total: 0,
})
}),
},
})
const paymentProviderService = {

View File

@@ -328,7 +328,7 @@ class OrderService extends BaseService {
query.select = select
}
const raw = await orderRepo.findOne(query)
const raw = await orderRepo.findOneWithRelations(query.relations, query)
if (!raw) {
throw new MedusaError(

File diff suppressed because it is too large Load Diff