Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,35 @@ export function consumerRouter(): Router {
);
const deliveries = await AppDataSource.getRepository(Delivery)
.createQueryBuilder("d")
// The `payload` snapshot is deliberately excluded: it is a full
// event body per row and nothing here renders it, so selecting it
// would drag every matched row's jsonb out of TOAST for nothing.
.select([
"d.id",
"d.subscriptionId",
"d.packetId",
"d.status",
"d.attempts",
"d.nextAttemptAt",
"d.lastError",
"d.lastResponseStatus",
"d.createdAt",
"d.deliveredAt",
])
.innerJoin(
Subscription,
"s",
"s.id = d.subscriptionId AND s.consumerId = :cid",
{ cid: req.consumer!.id },
)
.orderBy("d.createdAt", "DESC")
.take(limit)
// `limit`, not `take`: with a join present `take` makes TypeORM
// wrap the query in a SELECT DISTINCT over an *unbounded* subquery
// and apply LIMIT only on the outside, so Postgres materialises and
// sorts the consumer's entire delivery history to return 50 rows.
// The join is to subscriptions on its primary key and so cannot
// duplicate rows, which is the only thing that DISTINCT pass buys.
.limit(limit)
.getMany();
res.json({ deliveries });
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type DeliveryStatus =
"packetId",
"contentHash",
])
// Serves the consumer dashboard's newest-first delivery list.
@Index("idx_deliveries_subscription_created", ["subscriptionId", "createdAt"])
export class Delivery {
@PrimaryGeneratedColumn("uuid")
id!: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from "typeorm";

/**
* Support `/api/me/deliveries`, which lists a consumer's most recent deliveries
* newest-first. Without a (subscriptionId, createdAt) index that read sorts the
* consumer's entire delivery history on every dashboard load; the existing
* idx_deliveries_subscription can find the rows but cannot supply the ordering.
*
* NOTE for large deployments: `deliveries` is the hottest write table in the
* service and a plain CREATE INDEX holds a SHARE lock - blocking the delivery
* engine's writes - for as long as the build takes. Migrations here run inside
* a single transaction (typeorm's default "all" mode), which rules out
* CONCURRENTLY. So on a big table, build it by hand first:
*
* CREATE INDEX CONCURRENTLY "idx_deliveries_subscription_created"
* ON "deliveries" ("subscriptionId", "createdAt" DESC);
*
* The IF NOT EXISTS below then makes this migration a no-op.
*/
export class AddDeliveriesSubscriptionCreatedIndex1786492800000
implements MigrationInterface
{
name = "AddDeliveriesSubscriptionCreatedIndex1786492800000";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "idx_deliveries_subscription_created"
ON "deliveries" ("subscriptionId", "createdAt" DESC)`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX IF EXISTS "idx_deliveries_subscription_created"`,
);
}
}
Loading