Merge pull request #3592 from backstage/freben/relations-all-at-once

catalog-backend: read all relations at once
This commit is contained in:
Fredrik Adelöw
2020-12-06 19:49:37 +01:00
committed by GitHub
3 changed files with 58 additions and 24 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Batch the fetching of relations
@@ -271,7 +271,7 @@ export class CommonDatabase implements Database {
.select('entities.*')
.orderBy('full_name', 'asc');
return Promise.all(rows.map(row => this.toEntityResponse(tx, row)));
return this.toEntityResponses(tx, rows);
}
async entityByName(
@@ -290,7 +290,7 @@ export class CommonDatabase implements Database {
return undefined;
}
return this.toEntityResponse(tx, rows[0]);
return this.toEntityResponses(tx, rows).then(r => r[0]);
}
async entityByUid(
@@ -307,7 +307,7 @@ export class CommonDatabase implements Database {
return undefined;
}
return this.toEntityResponse(tx, rows[0]);
return this.toEntityResponses(tx, rows).then(r => r[0]);
}
async removeEntityByUid(txOpaque: Transaction, uid: string): Promise<void> {
@@ -494,31 +494,60 @@ export class CommonDatabase implements Database {
};
}
private async toEntityResponse(
private async toEntityResponses(
tx: Knex.Transaction<any, any>,
row: DbEntitiesRow,
): Promise<DbEntityResponse> {
const entity = JSON.parse(row.data) as Entity;
entity.metadata.uid = row.id;
entity.metadata.etag = row.etag;
entity.metadata.generation = Number(row.generation); // cast due to sqlite
rows: DbEntitiesRow[],
): Promise<DbEntityResponse[]> {
// TODO(Rugvip): This is here because it's simple for now, but we likely
// need to refactor this to be more efficient or introduce pagination.
const relations = await tx<DbEntitiesRelationsRow>('entities_relations')
.where({ source_full_name: row.full_name })
.orderBy(['type', 'target_full_name'])
.select();
const relations = await this.getRelationsPerFullName(
tx,
rows.map(r => r.full_name),
);
entity.relations = deduplicateRelations(relations).map(r => ({
target: parseEntityName(r.target_full_name),
type: r.type,
}));
const result = new Array<DbEntityResponse>();
for (const row of rows) {
const entity = JSON.parse(row.data) as Entity;
entity.metadata.uid = row.id;
entity.metadata.etag = row.etag;
entity.metadata.generation = Number(row.generation); // cast due to sqlite
return {
locationId: row.location_id || undefined,
entity,
};
entity.relations = (relations[row.full_name] ?? []).map(r => ({
target: parseEntityName(r.target_full_name),
type: r.type,
}));
result.push({
locationId: row.location_id || undefined,
entity,
});
}
return result;
}
// Returns a mapping from e.g. component:default/foo to the relations whose
// source_full_name matches that.
private async getRelationsPerFullName(
tx: Knex.Transaction<any, any>,
sourceFullNames: string[],
): Promise<Record<string, DbEntitiesRelationsRow[]>> {
const batches = lodash.chunk(lodash.uniq(sourceFullNames), 500);
const relations = new Array<DbEntitiesRelationsRow>();
for (const batch of batches) {
relations.push(
...(await tx<DbEntitiesRelationsRow>('entities_relations')
.whereIn('source_full_name', batch)
.orderBy(['type', 'target_full_name'])
.select()),
);
}
return lodash.groupBy(
deduplicateRelations(relations),
r => r.source_full_name,
);
}
}
@@ -85,7 +85,7 @@ export class HigherOrderOperations implements HigherOrderOperation {
// Write
if (!previousLocation && !dryRun) {
// TODO: We do not include location operations in the dryRun. We might perform
// this operation as a seperate dry run.
// this operation as a separate dry run.
await this.locationsCatalog.addLocation(location);
}
if (readerOutput.entities.length === 0) {