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
@@ -6,6 +6,7 @@ class MockRepo {
softRemove, softRemove,
find, find,
findOne, findOne,
findOneWithRelations,
findOneOrFail, findOneOrFail,
save, save,
findAndCount, findAndCount,
@@ -19,6 +20,7 @@ class MockRepo {
this.findOneOrFail_ = findOneOrFail; this.findOneOrFail_ = findOneOrFail;
this.save_ = save; this.save_ = save;
this.findAndCount_ = findAndCount; this.findAndCount_ = findAndCount;
this.findOneWithRelations_ = findOneWithRelations;
} }
setFindOne(fn) { setFindOne(fn) {
@@ -53,6 +55,11 @@ class MockRepo {
return this.findOneOrFail_(...args); return this.findOneOrFail_(...args);
} }
}); });
findOneWithRelations = jest.fn().mockImplementation((...args) => {
if (this.findOneWithRelations_) {
return this.findOneWithRelations_(...args);
}
});
findOne = jest.fn().mockImplementation((...args) => { findOne = jest.fn().mockImplementation((...args) => {
if (this.findOne_) { if (this.findOne_) {
return this.findOne_(...args); return this.findOne_(...args);
File diff suppressed because it is too large Load Diff
+53 -2
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" import { Order } from "../models/order"
@EntityRepository(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]
}
}
+15 -18
View File
@@ -396,7 +396,7 @@ describe("OrderService", () => {
describe("retrieve", () => { describe("retrieve", () => {
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: q => {
return Promise.resolve({}) return Promise.resolve({})
}, },
}) })
@@ -412,8 +412,8 @@ describe("OrderService", () => {
it("calls order model functions", async () => { it("calls order model functions", async () => {
await orderService.retrieve(IdMap.getId("test-order")) await orderService.retrieve(IdMap.getId("test-order"))
expect(orderRepo.findOne).toHaveBeenCalledTimes(1) expect(orderRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
expect(orderRepo.findOne).toHaveBeenCalledWith({ expect(orderRepo.findOneWithRelations).toHaveBeenCalledWith(undefined, {
where: { id: IdMap.getId("test-order") }, where: { id: IdMap.getId("test-order") },
}) })
}) })
@@ -446,7 +446,7 @@ describe("OrderService", () => {
describe("update", () => { describe("update", () => {
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
case IdMap.getId("fulfilled-order"): case IdMap.getId("fulfilled-order"):
return Promise.resolve({ return Promise.resolve({
@@ -527,7 +527,7 @@ describe("OrderService", () => {
describe("cancel", () => { describe("cancel", () => {
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
case IdMap.getId("paid-order"): case IdMap.getId("paid-order"):
return Promise.resolve({ return Promise.resolve({
@@ -606,7 +606,7 @@ describe("OrderService", () => {
describe("capturePayment", () => { describe("capturePayment", () => {
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
case IdMap.getId("fail"): case IdMap.getId("fail"):
return Promise.resolve({ return Promise.resolve({
@@ -718,7 +718,7 @@ describe("OrderService", () => {
} }
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
case "partial": case "partial":
return Promise.resolve(partialOrder) return Promise.resolve(partialOrder)
@@ -870,7 +870,7 @@ describe("OrderService", () => {
payments: [{ id: "payment_test" }], payments: [{ id: "payment_test" }],
} }
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
default: default:
return Promise.resolve(order) return Promise.resolve(order)
@@ -1043,7 +1043,7 @@ describe("OrderService", () => {
payments: [{ id: "payment_test" }], payments: [{ id: "payment_test" }],
} }
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
default: default:
return Promise.resolve(order) return Promise.resolve(order)
@@ -1129,7 +1129,7 @@ describe("OrderService", () => {
} }
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: q => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
case IdMap.getId("partial"): case IdMap.getId("partial"):
return Promise.resolve(partialOrder) return Promise.resolve(partialOrder)
@@ -1206,9 +1206,7 @@ describe("OrderService", () => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: jest findOneWithRelations: () => Promise.resolve({ id: IdMap.getId("order") }),
.fn()
.mockReturnValue(Promise.resolve({ id: IdMap.getId("order") })),
}) })
it("fails if order/swap relationship not satisfied", async () => { it("fails if order/swap relationship not satisfied", async () => {
@@ -1268,7 +1266,7 @@ describe("OrderService", () => {
it("registers a swap as received", async () => { it("registers a swap as received", async () => {
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: jest.fn().mockReturnValue( findOneWithRelations: () =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("order_123"), id: IdMap.getId("order_123"),
items: [ items: [
@@ -1278,8 +1276,7 @@ describe("OrderService", () => {
quantity: 1, quantity: 1,
}, },
], ],
}) }),
),
}) })
const swapService = { const swapService = {
@@ -1329,7 +1326,7 @@ describe("OrderService", () => {
}) })
const orderRepo = MockRepository({ const orderRepo = MockRepository({
findOne: jest.fn().mockImplementation(q => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cannot")) { if (q.where.id === IdMap.getId("cannot")) {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("order"), id: IdMap.getId("order"),
@@ -1353,7 +1350,7 @@ describe("OrderService", () => {
total: 100, total: 100,
refunded_total: 0, refunded_total: 0,
}) })
}), },
}) })
const paymentProviderService = { const paymentProviderService = {
+1 -1
View File
@@ -328,7 +328,7 @@ class OrderService extends BaseService {
query.select = select query.select = select
} }
const raw = await orderRepo.findOne(query) const raw = await orderRepo.findOneWithRelations(query.relations, query)
if (!raw) { if (!raw) {
throw new MedusaError( throw new MedusaError(
+11 -774
View File
File diff suppressed because it is too large Load Diff