improve: target time finding
This commit is contained in:
parent
f9dd53c250
commit
0be961e709
@ -22,11 +22,14 @@ export async function getVideosNearMilestone(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getLatestVideoSnapshot(client: Client, aid: number): Promise<null | LatestSnapshotType> {
|
export async function getLatestVideoSnapshot(client: Client, aid: number): Promise<null | LatestSnapshotType> {
|
||||||
const queryResult = await client.queryObject<LatestSnapshotType>(`
|
const queryResult = await client.queryObject<LatestSnapshotType>(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM latest_video_snapshot
|
FROM latest_video_snapshot
|
||||||
WHERE aid = $1
|
WHERE aid = $1
|
||||||
`, [aid]);
|
`,
|
||||||
|
[aid],
|
||||||
|
);
|
||||||
if (queryResult.rows.length === 0) {
|
if (queryResult.rows.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -35,6 +38,6 @@ export async function getLatestVideoSnapshot(client: Client, aid: number): Promi
|
|||||||
...row,
|
...row,
|
||||||
aid: Number(row.aid),
|
aid: Number(row.aid),
|
||||||
time: new Date(row.time).getTime(),
|
time: new Date(row.time).getTime(),
|
||||||
}
|
};
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {DAY, HOUR, MINUTE} from "$std/datetime/constants.ts";
|
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||||
import {Client} from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
import { formatTimestampToPsql } from "lib/utils/formatTimestampToPostgre.ts";
|
||||||
import {formatTimestampToPsql} from "lib/utils/formatTimestampToPostgre.ts";
|
import { SnapshotScheduleType } from "./schema.d.ts";
|
||||||
import {SnapshotScheduleType} from "./schema.d.ts";
|
|
||||||
import logger from "lib/log/logger.ts";
|
import logger from "lib/log/logger.ts";
|
||||||
|
import { MINUTE } from "$std/datetime/constants.ts";
|
||||||
|
|
||||||
export async function snapshotScheduleExists(client: Client, id: number) {
|
export async function snapshotScheduleExists(client: Client, id: number) {
|
||||||
const res = await client.queryObject<{ id: number }>(
|
const res = await client.queryObject<{ id: number }>(
|
||||||
@ -120,78 +120,73 @@ export async function scheduleSnapshot(client: Client, aid: number, type: string
|
|||||||
* Adjust the trigger time of the snapshot to ensure it does not exceed the frequency limit
|
* Adjust the trigger time of the snapshot to ensure it does not exceed the frequency limit
|
||||||
* @param client PostgreSQL client
|
* @param client PostgreSQL client
|
||||||
* @param expectedStartTime The expected snapshot time
|
* @param expectedStartTime The expected snapshot time
|
||||||
* @param allowedCounts The number of snapshots allowed in the 5-minutes windows.
|
* @param allowedCounts The number of snapshots allowed in a 5-minute window (default: 2000)
|
||||||
* @returns The adjusted actual snapshot time
|
* @returns The adjusted actual snapshot time within the first available window
|
||||||
*/
|
*/
|
||||||
export async function adjustSnapshotTime(
|
export async function adjustSnapshotTime(
|
||||||
client: Client,
|
client: Client,
|
||||||
expectedStartTime: Date,
|
expectedStartTime: Date,
|
||||||
allowedCounts: number = 2000
|
allowedCounts: number = 2000,
|
||||||
): Promise<Date> {
|
): Promise<Date> {
|
||||||
|
// Query to find the closest available window by checking both past and future windows
|
||||||
const findWindowQuery = `
|
const findWindowQuery = `
|
||||||
WITH windows AS (
|
WITH base AS (
|
||||||
SELECT generate_series(
|
SELECT
|
||||||
$1::timestamp, -- Start time: current time truncated to the nearest 5-minute window
|
date_trunc('minute', $1::timestamp)
|
||||||
$2::timestamp, -- End time: 24 hours after the target time window starts
|
- (EXTRACT(minute FROM $1::timestamp)::int % 5 * INTERVAL '1 minute') AS base_time
|
||||||
INTERVAL '5 MINUTES'
|
),
|
||||||
) AS window_start
|
offsets AS (
|
||||||
|
SELECT generate_series(-100, 100) AS "offset"
|
||||||
|
),
|
||||||
|
candidate_windows AS (
|
||||||
|
SELECT
|
||||||
|
(base.base_time + ("offset" * INTERVAL '5 minutes')) AS window_start,
|
||||||
|
ABS("offset") AS distance
|
||||||
|
FROM base
|
||||||
|
CROSS JOIN offsets
|
||||||
)
|
)
|
||||||
SELECT w.window_start
|
SELECT
|
||||||
FROM windows w
|
window_start
|
||||||
LEFT JOIN snapshot_schedule s ON s.started_at >= w.window_start
|
FROM
|
||||||
AND s.started_at < w.window_start + INTERVAL '5 MINUTES'
|
candidate_windows cw
|
||||||
|
LEFT JOIN
|
||||||
|
snapshot_schedule s
|
||||||
|
ON
|
||||||
|
s.started_at >= cw.window_start
|
||||||
|
AND s.started_at < cw.window_start + INTERVAL '5 minutes'
|
||||||
AND s.status = 'pending'
|
AND s.status = 'pending'
|
||||||
GROUP BY w.window_start
|
GROUP BY
|
||||||
HAVING COUNT(s.*) < ${allowedCounts}
|
cw.window_start, cw.distance
|
||||||
ORDER BY w.window_start
|
HAVING
|
||||||
|
COUNT(s.*) < $2
|
||||||
|
ORDER BY
|
||||||
|
cw.distance, cw.window_start
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
`;
|
`;
|
||||||
const now = new Date();
|
|
||||||
const targetTime = expectedStartTime.getTime();
|
|
||||||
let start = new Date(targetTime - 2 * HOUR);
|
|
||||||
if (start.getTime() <= now.getTime()) {
|
|
||||||
start = now;
|
|
||||||
}
|
|
||||||
const startTruncated = truncateTo5MinInterval(start);
|
|
||||||
const end = new Date(startTruncated.getTime() + 1 * DAY);
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Execute query to find the first available window
|
||||||
const windowResult = await client.queryObject<{ window_start: Date }>(
|
const windowResult = await client.queryObject<{ window_start: Date }>(
|
||||||
findWindowQuery,
|
findWindowQuery,
|
||||||
[startTruncated, end],
|
[expectedStartTime, allowedCounts],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If no available window found, return original time (may exceed limit)
|
||||||
const windowStart = windowResult.rows[0]?.window_start;
|
if (windowResult.rows.length === 0) {
|
||||||
if (!windowStart) {
|
|
||||||
return expectedStartTime;
|
return expectedStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (windowStart.getTime() > new Date().getTime() + 5 * MINUTE) {
|
// Get the target window start time
|
||||||
|
const windowStart = windowResult.rows[0].window_start;
|
||||||
|
|
||||||
|
// Add random delay within the 5-minute window to distribute load
|
||||||
const randomDelay = Math.floor(Math.random() * 5 * MINUTE);
|
const randomDelay = Math.floor(Math.random() * 5 * MINUTE);
|
||||||
return new Date(windowStart.getTime() + randomDelay);
|
return new Date(windowStart.getTime() + randomDelay);
|
||||||
} else {
|
} catch {
|
||||||
return expectedStartTime;
|
return expectedStartTime; // Fallback to original time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Truncate the timestamp to the nearest 5-minute interval
|
|
||||||
* @param timestamp The timestamp
|
|
||||||
* @returns The truncated time
|
|
||||||
*/
|
|
||||||
function truncateTo5MinInterval(timestamp: Date): Date {
|
|
||||||
const minutes = timestamp.getMinutes() - (timestamp.getMinutes() % 5);
|
|
||||||
return new Date(
|
|
||||||
timestamp.getFullYear(),
|
|
||||||
timestamp.getMonth(),
|
|
||||||
timestamp.getDate(),
|
|
||||||
timestamp.getHours(),
|
|
||||||
minutes,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSnapshotsInNextSecond(client: Client) {
|
export async function getSnapshotsInNextSecond(client: Client) {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT *
|
SELECT *
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Job } from "bullmq";
|
import { Job } from "bullmq";
|
||||||
import { db } from "lib/db/init.ts";
|
import { db } from "lib/db/init.ts";
|
||||||
import {getLatestVideoSnapshot, getVideosNearMilestone} from "lib/db/snapshot.ts";
|
import { getLatestVideoSnapshot, getVideosNearMilestone } from "lib/db/snapshot.ts";
|
||||||
import {
|
import {
|
||||||
findClosestSnapshot,
|
findClosestSnapshot,
|
||||||
getLatestSnapshot,
|
getLatestSnapshot,
|
||||||
getSnapshotsInNextSecond, getVideosWithoutActiveSnapshotSchedule,
|
getSnapshotsInNextSecond,
|
||||||
|
getVideosWithoutActiveSnapshotSchedule,
|
||||||
hasAtLeast2Snapshots,
|
hasAtLeast2Snapshots,
|
||||||
scheduleSnapshot,
|
scheduleSnapshot,
|
||||||
setSnapshotStatus,
|
setSnapshotStatus,
|
||||||
@ -12,7 +13,7 @@ import {
|
|||||||
videoHasProcessingSchedule,
|
videoHasProcessingSchedule,
|
||||||
} from "lib/db/snapshotSchedule.ts";
|
} from "lib/db/snapshotSchedule.ts";
|
||||||
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||||
import { WEEK, HOUR, MINUTE, SECOND } from "$std/datetime/constants.ts";
|
import { HOUR, MINUTE, SECOND, WEEK } from "$std/datetime/constants.ts";
|
||||||
import logger from "lib/log/logger.ts";
|
import logger from "lib/log/logger.ts";
|
||||||
import { SnapshotQueue } from "lib/mq/index.ts";
|
import { SnapshotQueue } from "lib/mq/index.ts";
|
||||||
import { insertVideoSnapshot } from "lib/mq/task/getVideoStats.ts";
|
import { insertVideoSnapshot } from "lib/mq/task/getVideoStats.ts";
|
||||||
@ -107,6 +108,7 @@ const getAdjustedShortTermETA = async (client: Client, aid: number) => {
|
|||||||
|
|
||||||
export const collectMilestoneSnapshotsWorker = async (_job: Job) => {
|
export const collectMilestoneSnapshotsWorker = async (_job: Job) => {
|
||||||
const client = await db.connect();
|
const client = await db.connect();
|
||||||
|
const startedAt = Date.now();
|
||||||
try {
|
try {
|
||||||
const videos = await getVideosNearMilestone(client);
|
const videos = await getVideosNearMilestone(client);
|
||||||
for (const video of videos) {
|
for (const video of videos) {
|
||||||
@ -120,6 +122,9 @@ export const collectMilestoneSnapshotsWorker = async (_job: Job) => {
|
|||||||
const delay = truncate(scheduledNextSnapshotDelay, minInterval, maxInterval);
|
const delay = truncate(scheduledNextSnapshotDelay, minInterval, maxInterval);
|
||||||
const targetTime = now + delay;
|
const targetTime = now + delay;
|
||||||
await scheduleSnapshot(client, aid, "milestone", targetTime);
|
await scheduleSnapshot(client, aid, "milestone", targetTime);
|
||||||
|
if (now - startedAt > 25 * MINUTE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e as Error, "mq", "fn:collectMilestoneSnapshotsWorker");
|
logger.error(e as Error, "mq", "fn:collectMilestoneSnapshotsWorker");
|
||||||
|
@ -35,7 +35,7 @@ export async function initMQ() {
|
|||||||
immediately: true,
|
immediately: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await SnapshotQueue.removeJobScheduler('scheduleSnapshotTick');
|
await SnapshotQueue.removeJobScheduler("scheduleSnapshotTick");
|
||||||
|
|
||||||
logger.log("Message queue initialized.");
|
logger.log("Message queue initialized.");
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ import { lockManager } from "lib/mq/lockManager.ts";
|
|||||||
import { WorkerError } from "lib/mq/schema.ts";
|
import { WorkerError } from "lib/mq/schema.ts";
|
||||||
import { getVideoInfoWorker } from "lib/mq/exec/getLatestVideos.ts";
|
import { getVideoInfoWorker } from "lib/mq/exec/getLatestVideos.ts";
|
||||||
import {
|
import {
|
||||||
collectMilestoneSnapshotsWorker, regularSnapshotsWorker,
|
collectMilestoneSnapshotsWorker,
|
||||||
|
regularSnapshotsWorker,
|
||||||
snapshotTickWorker,
|
snapshotTickWorker,
|
||||||
takeSnapshotForVideoWorker,
|
takeSnapshotForVideoWorker,
|
||||||
} from "lib/mq/exec/snapshotTick.ts";
|
} from "lib/mq/exec/snapshotTick.ts";
|
||||||
|
Loading…
Reference in New Issue
Block a user