-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
202 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import _ from "lodash"; | ||
import { Op } from "sequelize"; | ||
|
||
import { destroyConversation } from "@app/lib/api/assistant/conversation/destroy"; | ||
import { Authenticator } from "@app/lib/auth"; | ||
import { Conversation } from "@app/lib/models/assistant/conversation"; | ||
import { Workspace } from "@app/lib/models/workspace"; | ||
import logger from "@app/logger/logger"; | ||
|
||
/** | ||
* Get workspace ids with conversations retention policy. | ||
*/ | ||
export async function getWorkspacesWithConversationsRetentionActivity(): Promise< | ||
number[] | ||
> { | ||
const workspaces = await Workspace.findAll({ | ||
attributes: ["id"], | ||
where: { | ||
conversationsRetentionDays: { | ||
[Op.not]: null, | ||
}, | ||
}, | ||
}); | ||
return workspaces.map((w) => w.id); | ||
} | ||
|
||
/** | ||
* Purge conversations for workspaces with retention policy. | ||
* We chunk the workspaces to avoid hitting the database with too many queries at once. | ||
*/ | ||
export async function purgeConversationsBatchActivity({ | ||
workspaceIds, | ||
}: { | ||
workspaceIds: number[]; | ||
}) { | ||
for (const workspaceId of workspaceIds) { | ||
const workspace = await Workspace.findByPk(workspaceId); | ||
if (!workspace) { | ||
logger.error( | ||
{ workspaceId }, | ||
"Workspace with retention policy not found." | ||
); | ||
continue; | ||
} | ||
if (!workspace.conversationsRetentionDays) { | ||
logger.error( | ||
{ workspaceId }, | ||
"Workspace with retention policy has no retention days." | ||
); | ||
continue; | ||
} | ||
const retentionDays = workspace.conversationsRetentionDays; | ||
const cutoffDate = new Date(); | ||
cutoffDate.setDate(cutoffDate.getDate() - retentionDays); | ||
|
||
const conversations = await Conversation.findAll({ | ||
where: { workspaceId: workspace.id, updatedAt: { [Op.lt]: cutoffDate } }, | ||
}); | ||
|
||
logger.info( | ||
{ | ||
workspaceId, | ||
retentionDays, | ||
cutoffDate, | ||
nbConversations: conversations.length, | ||
}, | ||
"Purging conversations for workspace." | ||
); | ||
|
||
const auth = await Authenticator.internalAdminForWorkspace(workspace.sId); | ||
|
||
const conversationChunks = _.chunk(conversations, 4); | ||
for (const conversationChunk of conversationChunks) { | ||
await Promise.all( | ||
conversationChunk.map(async (c) => { | ||
await destroyConversation(auth, { conversationId: c.sId }); | ||
}) | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import type { Result } from "@dust-tt/types"; | ||
import { Err, Ok } from "@dust-tt/types"; | ||
import { | ||
ScheduleAlreadyRunning, | ||
ScheduleOverlapPolicy, | ||
} from "@temporalio/client"; | ||
|
||
import { getTemporalClient } from "@app/lib/temporal"; | ||
import logger from "@app/logger/logger"; | ||
import { getPurgeDataRetentionScheduleId } from "@app/temporal/data_retention/utils"; | ||
import { purgeDataRetentionWorkflow } from "@app/temporal/data_retention/workflows"; | ||
|
||
import { QUEUE_NAME } from "./config"; | ||
|
||
/** | ||
* This function starts a schedule to purge workspaces set up with retention policy (only concern conversations at the moment). | ||
*/ | ||
export async function launchPurgeDataRetentionSchedule(): Promise< | ||
Result<undefined, Error> | ||
> { | ||
const client = await getTemporalClient(); | ||
const scheduleId = getPurgeDataRetentionScheduleId(); | ||
|
||
try { | ||
await client.schedule.create({ | ||
action: { | ||
type: "startWorkflow", | ||
workflowType: purgeDataRetentionWorkflow, | ||
args: [], | ||
taskQueue: QUEUE_NAME, | ||
}, | ||
scheduleId, | ||
policies: { | ||
overlap: ScheduleOverlapPolicy.SKIP, | ||
}, | ||
spec: { | ||
intervals: [{ every: "24h" }], | ||
}, | ||
}); | ||
} catch (err) { | ||
if (!(err instanceof ScheduleAlreadyRunning)) { | ||
logger.error({}, "Failed to start purge data retention schedule."); | ||
|
||
return new Err(err as Error); | ||
} | ||
} | ||
|
||
return new Ok(undefined); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const QUEUE_VERSION = 1; | ||
|
||
export const QUEUE_NAME = `data-retention-queue-v${QUEUE_VERSION}`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* Purge Data retention logic. | ||
*/ | ||
|
||
export function getPurgeDataRetentionScheduleId() { | ||
return "purge-data-retention-schedule"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type { Context } from "@temporalio/activity"; | ||
import { Worker } from "@temporalio/worker"; | ||
|
||
import { getTemporalWorkerConnection } from "@app/lib/temporal"; | ||
import { ActivityInboundLogInterceptor } from "@app/lib/temporal_monitoring"; | ||
import logger from "@app/logger/logger"; | ||
import { launchPurgeDataRetentionSchedule } from "@app/temporal/data_retention/client"; | ||
import * as activities from "@app/temporal/hard_delete/activities"; | ||
|
||
import { QUEUE_NAME } from "./config"; | ||
|
||
export async function runDataRetentionWorker() { | ||
const { connection, namespace } = await getTemporalWorkerConnection(); | ||
const worker = await Worker.create({ | ||
workflowsPath: require.resolve("./workflows"), | ||
activities, | ||
taskQueue: QUEUE_NAME, | ||
maxConcurrentActivityTaskExecutions: 32, | ||
connection, | ||
namespace, | ||
interceptors: { | ||
activityInbound: [ | ||
(ctx: Context) => { | ||
return new ActivityInboundLogInterceptor(ctx, logger); | ||
}, | ||
], | ||
}, | ||
}); | ||
|
||
// Start the schedule. | ||
await launchPurgeDataRetentionSchedule(); | ||
|
||
await worker.run(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { proxyActivities } from "@temporalio/workflow"; | ||
import _ from "lodash"; | ||
|
||
import type * as activities from "@app/temporal/data_retention/activities"; | ||
|
||
const { | ||
getWorkspacesWithConversationsRetentionActivity, | ||
purgeConversationsBatchActivity, | ||
} = proxyActivities<typeof activities>({ | ||
startToCloseTimeout: "15 minutes", | ||
}); | ||
|
||
export async function purgeDataRetentionWorkflow(): Promise<void> { | ||
const workspaceIds = await getWorkspacesWithConversationsRetentionActivity(); | ||
const workspaceChunks = _.chunk(workspaceIds, 4); | ||
|
||
for (const workspaceChunk of workspaceChunks) { | ||
await purgeConversationsBatchActivity({ | ||
workspaceIds: workspaceChunk, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.