You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于另一集合的TTL配置MongoDB动态自动删除文档

解决方案:NestJS + MongoDB 实现跨集合关联的动态TTL自动删除

一、核心原理说明

MongoDB的TTL索引支持两种模式:固定秒数过期,或基于文档内具体日期字段自动过期。后者是实现单文档不同TTL的关键——只需将expireAfterSeconds设为0,同时给每个文档添加expireAt字段(存储该文档的具体过期时间),MongoDB后台进程会自动删除到达该时间的文档。

结合你的场景,我们需要先从关联集合获取用户的TTL配置,计算出日志的expireAt值,再利用TTL索引实现自动删除;同时还要处理TTL配置变更时的同步逻辑。

二、步骤1:调整API日志集合的Schema

修改api-hit logs的Mongoose Schema,新增expireAt字段并创建对应TTL索引:

import { Schema, Document } from 'mongoose';

export interface ApiHitLog extends Document {
  username: string;
  createdOn: Date;
  expireAt: Date;
  // 其他日志字段(如接口路径、请求参数等)
}

export const ApiHitLogSchema = new Schema<ApiHitLog>({
  username: { type: String, required: true },
  createdOn: { type: Date, required: true, default: Date.now },
  expireAt: { type: Date, required: true },
  // 其他字段定义
}, { timestamps: true });

// 创建TTL索引:基于expireAt字段,expireAfterSeconds设为0
ApiHitLogSchema.index({ expireAt: 1 }, { expireAfterSeconds: 0 });

三、步骤2:插入日志时计算过期时间

插入日志前,先从关联集合(假设叫UserSettings,含username和ttlSeconds字段)获取用户的TTL配置,计算出expireAt(日志创建时间 + TTL秒数)后存入数据库:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { ApiHitLog } from './schemas/api-hit-log.schema';
import { UserSettings } from '../user-settings/schemas/user-settings.schema';

@Injectable()
export class ApiHitLogService {
  constructor(
    @InjectModel(ApiHitLog.name) private apiHitLogModel: Model<ApiHitLog>,
    @InjectModel(UserSettings.name) private userSettingsModel: Model<UserSettings>,
  ) {}

  async createLog(username: string, logData: Partial<ApiHitLog>) {
    // 获取用户TTL配置,无配置则用默认值(比如3600秒)
    const userSettings = await this.userSettingsModel.findOne({ username });
    const ttlSeconds = userSettings?.ttlSeconds ?? 3600;

    const createdOn = new Date();
    // 计算过期时间:创建时间转毫秒 + TTL秒数转毫秒
    const expireAt = new Date(createdOn.getTime() + ttlSeconds * 1000);

    const log = new this.apiHitLogModel({
      username,
      createdOn,
      expireAt,
      ...logData,
    });

    return log.save();
  }
}

四、步骤3:同步关联集合的TTL变更

如果用户的TTL配置在UserSettings中被修改,需要同步更新该用户所有未过期的API日志expireAt字段,否则旧日志仍会按原时间过期。

方式1:手动触发更新(修改TTL时调用)

在用户TTL修改的服务中,添加同步日志的逻辑:

async updateUserTTL(username: string, newTTLSeconds: number) {
  // 更新用户TTL配置
  await this.userSettingsModel.updateOne({ username }, { ttlSeconds: newTTLSeconds });

  // 批量更新未过期日志的expireAt
  await this.apiHitLogModel.updateMany(
    { username, expireAt: { $gt: new Date() } }, // 仅更新未过期的日志
    [
      {
        $set: {
          expireAt: { $add: ['$createdOn', newTTLSeconds * 1000] } // MongoDB日期运算,单位毫秒
        }
      }
    ]
  );
}

方式2:MongoDB变更流自动监听

若需完全自动化,可利用MongoDB的Change Streams监听UserSettings集合的更新事件,自动同步日志:

import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { UserSettings } from '../user-settings/schemas/user-settings.schema';
import { ApiHitLog } from './schemas/api-hit-log.schema';

@Injectable()
export class UserSettingsTTLListener implements OnModuleInit {
  constructor(
    @InjectModel(UserSettings.name) private userSettingsModel: Model<UserSettings>,
    @InjectModel(ApiHitLog.name) private apiHitLogModel: Model<ApiHitLog>,
  ) {}

  async onModuleInit() {
    const changeStream = this.userSettingsModel.watch();

    changeStream.on('change', async (change) => {
      // 仅处理TTL字段更新的事件
      if (change.operationType === 'update' && change.updateDescription?.updatedFields?.ttlSeconds) {
        const username = change.documentKey.username;
        const newTTLSeconds = change.updateDescription.updatedFields.ttlSeconds;

        // 同步更新未过期日志
        await this.apiHitLogModel.updateMany(
          { username, expireAt: { $gt: new Date() } },
          [
            {
              $set: {
                expireAt: { $add: ['$createdOn', newTTLSeconds * 1000] }
              }
            }
          ]
        );
      }
    });
  }
}

五、注意事项

  • MongoDB的TTL后台进程每60秒运行一次,文档可能会在过期后最多延迟60秒被删除,属于正常现象。
  • 确保expireAt字段为有效的Date类型,否则TTL索引不会生效。
  • 务必处理关联集合无TTL配置的场景,设置合理默认值避免日志永不过期。

内容的提问来源于stack exchange,提问作者Udayaprakash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:20:06