perf: batch inbound workflows by tenant
This commit is contained in:
@@ -2449,11 +2449,80 @@ describe('SendChainService', () => {
|
||||
const { service, prisma } = createService();
|
||||
prisma.$queryRaw.mockResolvedValueOnce([]);
|
||||
|
||||
await (service as any).submission.inboundEntry.claimInboundWorkflows(5);
|
||||
await (service as any).submission.inboundEntry.claimInboundWorkflows(5, ['tenant-busy']);
|
||||
|
||||
const sql = prisma.$queryRaw.mock.calls[0][0].strings.join(' ');
|
||||
expect(sql).toContain("NOW() AT TIME ZONE 'UTC'");
|
||||
expect(sql).toContain('"tenantId" NOT IN');
|
||||
expect(prisma.$queryRaw.mock.calls[0][0].values).toContain('tenant-busy');
|
||||
expect(sql).toContain('FOR UPDATE SKIP LOCKED');
|
||||
expect(sql).toContain('inbox."tenantId"');
|
||||
});
|
||||
|
||||
it('groups Inbox work by tenant and keeps chunks for one tenant serial', async () => {
|
||||
const { service } = createService();
|
||||
const inboundEntry = (service as any).submission.inboundEntry;
|
||||
const items = [
|
||||
{ id: 'a1', tenantId: 'tenant-a', applicationId: 'app-1' },
|
||||
{ id: 'b1', tenantId: 'tenant-b', applicationId: 'app-2' },
|
||||
{ id: 'a2', tenantId: 'tenant-a', applicationId: 'app-1' },
|
||||
{ id: 'a3', tenantId: 'tenant-a', applicationId: 'app-1' },
|
||||
];
|
||||
const grouped = inboundEntry.groupInboundWorkflowsByTenant(items);
|
||||
expect([...grouped.keys()]).toEqual(['tenant-a', 'tenant-b']);
|
||||
expect(grouped.get('tenant-a').map((item: { id: string }) => item.id)).toEqual(['a1', 'a2', 'a3']);
|
||||
|
||||
inboundEntry.processClaimedInboundWorkflowBatch = jest.fn().mockResolvedValue(undefined);
|
||||
await inboundEntry.processTenantInboundWorkflowBatches(grouped.get('tenant-a'), 2, new Map());
|
||||
expect(inboundEntry.processClaimedInboundWorkflowBatch.mock.calls.map((call: unknown[]) => (
|
||||
(call[0] as Array<{ id: string }>).map((item) => item.id)
|
||||
))).toEqual([['a1', 'a2'], ['a3']]);
|
||||
});
|
||||
|
||||
it('coalesces a small ready Inbox set before claiming the next tenant batch', async () => {
|
||||
const previousWait = process.env.API_INBOUND_WORKFLOW_BATCH_WAIT_MS;
|
||||
const previousTarget = process.env.API_INBOUND_WORKFLOW_TARGET_BATCH_SIZE;
|
||||
process.env.API_INBOUND_WORKFLOW_BATCH_WAIT_MS = '5';
|
||||
process.env.API_INBOUND_WORKFLOW_TARGET_BATCH_SIZE = '32';
|
||||
try {
|
||||
const { service, prisma } = createService();
|
||||
prisma.$queryRaw.mockResolvedValueOnce([{ count: 3n }]);
|
||||
const startedAt = Date.now();
|
||||
await (service as any).submission.inboundEntry.waitForInboundWorkflowMicroBatch(96, ['tenant-busy']);
|
||||
expect(Date.now() - startedAt).toBeGreaterThanOrEqual(4);
|
||||
const query = prisma.$queryRaw.mock.calls[0][0];
|
||||
expect(query.strings.join(' ')).toContain('COUNT(*)::bigint');
|
||||
expect(query.values).toContain('tenant-busy');
|
||||
} finally {
|
||||
if (previousWait == null) delete process.env.API_INBOUND_WORKFLOW_BATCH_WAIT_MS;
|
||||
else process.env.API_INBOUND_WORKFLOW_BATCH_WAIT_MS = previousWait;
|
||||
if (previousTarget == null) delete process.env.API_INBOUND_WORKFLOW_TARGET_BATCH_SIZE;
|
||||
else process.env.API_INBOUND_WORKFLOW_TARGET_BATCH_SIZE = previousTarget;
|
||||
}
|
||||
});
|
||||
|
||||
it('uses UTC for Submit Outbox claim, lease, publish, and retry timestamps', async () => {
|
||||
const previousShadow = process.env.SEND_SUBMIT_OUTBOX_SHADOW_ENABLED;
|
||||
const previousPublish = process.env.SEND_SUBMIT_OUTBOX_PUBLISH_ENABLED;
|
||||
process.env.SEND_SUBMIT_OUTBOX_SHADOW_ENABLED = 'true';
|
||||
delete process.env.SEND_SUBMIT_OUTBOX_PUBLISH_ENABLED;
|
||||
try {
|
||||
const { service, prisma } = createService();
|
||||
prisma.$queryRaw.mockResolvedValueOnce([{ id: 'outbox-1', submitId: 'SUB-1', payload: { messageId: 'MSG-1' } }]);
|
||||
const gatewaySubmit = (service as any).submission.gatewaySubmit;
|
||||
await gatewaySubmit.publishSubmitOutboxBatch();
|
||||
|
||||
const claimSql = prisma.$queryRaw.mock.calls[0][0].strings.join(' ');
|
||||
const publishSql = prisma.$executeRaw.mock.calls.at(-1)[0].strings.join(' ');
|
||||
expect(claimSql).toContain("NOW() AT TIME ZONE 'UTC'");
|
||||
expect(publishSql).toContain("NOW() AT TIME ZONE 'UTC'");
|
||||
expect(`${claimSql} ${publishSql}`).not.toContain('CURRENT_TIMESTAMP');
|
||||
} finally {
|
||||
if (previousShadow == null) delete process.env.SEND_SUBMIT_OUTBOX_SHADOW_ENABLED;
|
||||
else process.env.SEND_SUBMIT_OUTBOX_SHADOW_ENABLED = previousShadow;
|
||||
if (previousPublish == null) delete process.env.SEND_SUBMIT_OUTBOX_PUBLISH_ENABLED;
|
||||
else process.env.SEND_SUBMIT_OUTBOX_PUBLISH_ENABLED = previousPublish;
|
||||
}
|
||||
});
|
||||
|
||||
it('enqueues a freshly persisted inbound message without querying the task and message again', async () => {
|
||||
|
||||
Reference in New Issue
Block a user