๐Ÿ—‘๏ธ NestJS + Cron๋กœ ๊ตฌํ˜„ํ•œ ํœด์ง€ํ†ต ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ๊ธฐ

LinkDropperยท2025๋…„ 5์›” 16์ผ
8

Link Dropper

๋ชฉ๋ก ๋ณด๊ธฐ
9/12
post-thumbnail

๋งํฌ ๋“œ๋ผํผ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์ˆ˜๋งŽ์€ ๋งํฌ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ, ์‚ญ์ œ๋Š” ๋Š˜ ๊ณ ๋ฏผ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
โ€œ์‹ค์ˆ˜๋กœ ์‚ญ์ œํ•œ ๋งํฌ๋Š” ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์„๊นŒ?โ€, โ€œ์ง€์› ์ง€๋งŒ ๋‹ค์‹œ ๋ณด๊ณ  ์‹ถ์–ด์ง€๋ฉด?โ€
๊ทธ๋ž˜์„œ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํœด์ง€ํ†ต ์‹œ์Šคํ…œ.


๐Ÿ“Œ ์‚ญ์ œํ•˜๋ฉด ํœด์ง€ํ†ต์œผ๋กœ ์ด๋™๋ฉ๋‹ˆ๋‹ค

๋ณด๊ด€ ์ค‘์ธ ๋งํฌ๋‚˜ ํด๋”๋ฅผ ์‚ญ์ œํ•˜๋ฉด ๋ฐ”๋กœ DB์—์„œ ์‚ฌ๋ผ์ง€๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ํœด์ง€ํ†ต์œผ๋กœ ์ด๋™๋ฉ๋‹ˆ๋‹ค.

์ด์ œ ๋งํฌ ๋“œ๋ผํผ์˜ ์‚ญ์ œ๋Š” ๊ณง โ€˜30์ผ ๋™์•ˆ์˜ ์œ ์˜ˆโ€™๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

// ๋งํฌ ์‚ญ์ œ ์‹œ ์ฒ˜๋ฆฌ
PATCH /api/links/:id/trash

// ํด๋” ์‚ญ์ œ ์‹œ ์ฒ˜๋ฆฌ
PATCH /api/folders/:id/trash
// ์˜ˆ์‹œ ์ฝ”๋“œ (NestJS)
await this.linkRepository.update(id, {{
  isTrashed: true,
  trashedAt: new Date(),
}});
  • ์‚ญ์ œ๋Š” isTrashed = true์™€ trashedAt = now๋งŒ ์„ค์ •ํ•˜๋ฉด ๋!
  • UI์—์„œ๋Š” isTrashed ์ƒํƒœ์— ๋”ฐ๋ผ ํœด์ง€ํ†ต ์ „์šฉ ๋ทฐ๋กœ ๋ถ„๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • ๋ณต๊ตฌํ•  ์ˆ˜๋„ ์žˆ๊ณ , 30์ผ์ด ์ง€๋‚˜๋ฉด ์˜๊ตฌ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.

โณ 30์ผ์ด ์ง€๋‚˜๋ฉด ์ž๋™ ์˜๊ตฌ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค (cron ์‚ฌ์šฉ)

์‚ญ์ œํ•œ ๋งํฌ๋ฅผ 30์ผ ์ด์ƒ ๋ฐฉ์น˜ํ•˜๋ฉด?
๐Ÿ‘‰ softDelete๋กœ ์‹ค์ œ ์‚ญ์ œ ์ฒ˜๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฉ๋‹ˆ๋‹ค.

โฐ Cron์ด๋ž€?

Cron์€ ํŠน์ • ์‹œ๊ฐ„์— ํŠน์ • ์ž‘์—…์„ ๋ฐ˜๋ณต ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ์Šค์ผ€์ค„๋ง ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.
๋ฆฌ๋ˆ…์Šค ๊ธฐ๋ฐ˜ ์„œ๋ฒ„์—์„œ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋ฉฐ, NestJS์—์„œ๋Š” @nestjs/schedule ๋ชจ๋“ˆ๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ Cron ํ‘œํ˜„์‹ ์˜ˆ์‹œ

ํ‘œํ˜„์‹์„ค๋ช…
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)๋งค์ผ ์ž์ •์— ์‹คํ–‰
@Cron('0 0 * * *')๋งค์ผ ์ž์ •์— ์‹คํ–‰ (๋™์ผ ์˜๋ฏธ)
@Cron('0 */6 * * *')6์‹œ๊ฐ„๋งˆ๋‹ค ์‹คํ–‰
@Cron(CronExpression.EVERY_HOUR)๋งค ์ •์‹œ์— ์‹คํ–‰

NestJS์—์„œ Cron ์ž‘์—…์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ 3๊ฐ€์ง€๋งŒ ๊ธฐ์–ตํ•˜์„ธ์š”:

  1. @nestjs/schedule ์„ค์น˜ ๋ฐ ScheduleModule.forRoot() ๋“ฑ๋ก
  2. @Cron(...) ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ
  3. ์„œ๋น„์Šค ํด๋ž˜์Šค์— ์ž‘์—… ๋กœ์ง ๊ตฌํ˜„

์ •๋ฆฌ ์ž‘์—…, ํ†ต๊ณ„ ์ง‘๊ณ„, ์ž๋™ ์•Œ๋ฆผ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐฑ์—”๋“œ ์ž๋™ํ™” ์ž‘์—…์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ›  NestJS์—์„œ cron์œผ๋กœ ๊ตฌํ˜„

npm install @nestjs/schedule
// trash-cleanup.service.ts
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
async handleCron() {{
  const deadline = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const oldLinks = await this.linkRepo.find({{
    where: {{
      isTrashed: true,
      trashedAt: LessThan(deadline),
      deletedAt: null,
    }},
  }});

  await this.linkRepo.softRemove(oldLinks);
}}
  • ๋งค์ผ 00์‹œ์— ์‹คํ–‰๋˜์–ด 30์ผ ์ด์ƒ ํœด์ง€ํ†ต์— ์žˆ๋˜ ๋งํฌ/ํด๋”๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • softRemove๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋Š” ๋ฌผ๋ฆฌ ์‚ญ์ œ๋˜์ง€ ์•Š๊ณ , deletedAt๋งŒ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.
  • UI/ํ†ต๊ณ„์—์„œ ๋ณด์ด์ง€ ์•Š์ง€๋งŒ, ํ•„์š”ํ•˜๋‹ค๋ฉด ๊ด€๋ฆฌ์ž๊ฐ€ ๋ณต์›ํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์ฃ .

๐Ÿ”ฎ ์•ž์œผ๋กœ์˜ ๋ฐฉํ–ฅ์„ฑ: ์ฝ์ง€ ์•Š์€ ๋งํฌ, ์ด์ œ๋Š” ์ •๋ฆฌ๋  ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค

์šฐ๋ฆฌ๋Š” ๋งํฌ๋ฅผ ์ €์žฅํ•  ๋•Œ โ€œ๋‚˜์ค‘์— ๊ผญ ์ฝ์„ ๊ฑฐ์•ผโ€๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ํ˜„์‹ค์€... ์•ˆ ์ฝ๊ณ  ๋ฐฉ์น˜๋œ ๋งํฌ์˜ ๋ฌด๋ค.

๊ทธ๋ž˜์„œ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.

โ€œ3์ผ ๋™์•ˆ ์ฝ์ง€ ์•Š์€ ๋งํฌ๋Š” ์ž๋™ ์ •๋ฆฌํ•˜์ž.โ€


โ“ ์™œ ์ด๋Ÿฐ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ๊ฐ€์š”?

  • ๐Ÿ“Œ ๋ณด๊ด€ํ•จ์ด ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ ธ์„œ ์ •๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿง  ์ฝ์ง€ ์•Š์„ ๋งํฌ๋Š” ์žŠํžˆ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
  • โณ โ€œ๊ณง ์‚ฌ๋ผ์งˆ ๊ฑฐ๋‹คโ€๋ผ๋Š” ๋ฆฌ๋งˆ์ธ๋“œ๊ฐ€ ์˜คํžˆ๋ ค ์ฝ๋„๋ก ์œ ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์€ ๋‹จ์ˆœํžˆ ์‚ญ์ œ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž์—๊ฒŒ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋ฌป๋Š” ๊ฑฐ์ฃ :

โ€œ์ด ๋งํฌ, ์ง„์งœ ์•ˆ ์ฝ์œผ์‹ค ๊ฑด๊ฐ€์š”?โ€


๐Ÿ›  ์ž๋™ ์ •๋ฆฌ UX ํ๋ฆ„

โœ… ๊ธฐ๋ณธ ์ƒํƒœ

  • 3์ผ ์ด์ƒ ์ฝ์ง€ ์•Š์€ ๋งํฌ๊ฐ€ ์žˆ์œผ๋ฉด,
    ๐Ÿ‘‰ โ€œ์ž๋™ ์ •๋ฆฌ ์ œ์•ˆ ๋ชจ๋‹ฌโ€์„ ๋„์›Œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค.

๐Ÿ’ฌ โ€œ์ฝ์ง€ ์•Š์€ ๋งํฌ 5๊ฐœ๊ฐ€ ์žˆ์–ด์š”. ํœด์ง€ํ†ต์œผ๋กœ ์ •๋ฆฌํ• ๊นŒ์š”?โ€

  • ์‚ฌ์šฉ์ž๋Š” ๋งํฌ ํ™•์ธ ํ›„ ์ง์ ‘ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โš™๏ธ ์ž๋™ ์ •๋ฆฌ ๊ธฐ๋Šฅ์„ ์„ค์ •ํ•œ ๊ฒฝ์šฐ

  • ์‚ฌ์šฉ์ž๋Š” ์„ค์ •์—์„œ ์ž๋™ ์ •๋ฆฌ ๊ธฐ๋Šฅ์„ ์ผค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„ค์ • ์‹œ,
    ๐Ÿ‘‰ 3์ผ ์ด์ƒ ์ฝ์ง€ ์•Š์€ ๋งํฌ๋Š” ์ž๋™์œผ๋กœ ํœด์ง€ํ†ต์œผ๋กœ ์ด๋™๋ฉ๋‹ˆ๋‹ค.
  • ๋” ์ด์ƒ ๋ฌป์ง€ ์•Š๊ณ , ์กฐ์šฉํžˆ ๋ณด๊ด€ํ•จ์ด ๊น”๋”ํ•ด์ง‘๋‹ˆ๋‹ค.

๐Ÿงช ์˜ˆ์‹œ ์ฝ”๋“œ: ์ž๋™ ์ •๋ฆฌ ์ฒ˜๋ฆฌ

// 3์ผ ์ด์ƒ ์ฝ์ง€ ์•Š์€ ๋งํฌ โ†’ ํœด์ง€ํ†ต์œผ๋กœ ์ด๋™
const deadline = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);

const unreadLinks = await this.linkRepo.find({{
  where: {{
    isRead: false,
    createdAt: LessThan(deadline),
    isTrashed: false,
    userId: currentUser.id,
    userSetting: {{ autoTrashUnreadLinks: true }},
  }},
});

for (const link of unreadLinks) {{
  link.isTrashed = true;
  link.trashedAt = new Date();
  await this.linkRepo.save(link);
}}

โค๏ธ ์—ฌ๋Ÿฌ๋ถ„์˜ ์˜๊ฒฌ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

  • ์ด ์ž๋™ ์ •๋ฆฌ ๊ธฐ๋Šฅ, ๋งˆ์Œ์— ๋“œ์‹œ๋‚˜์š”?
  • โ€œ์ฝ์ง€ ์•Š์€ ๋งํฌ ์ •๋ฆฌโ€๋Š” ์ •๋ง ํ•„์š”ํ•˜๋‹ค๊ณ  ๋А๋ผ์‹œ๋‚˜์š”?

๐Ÿ‘‡ ์ข‹์•„์š”๋ฅผ ๋ˆŒ๋Ÿฌ์ฃผ์‹œ๊ฑฐ๋‚˜
๐Ÿ‘‡ ๋Œ“๊ธ€๋กœ ์˜๊ฒฌ์„ ๋‚จ๊ฒจ์ฃผ์„ธ์š”!

์—ฌ๋Ÿฌ๋ถ„์˜ ํ”ผ๋“œ๋ฐฑ์œผ๋กœ ๊ธฐ๋Šฅ์„ ์กฐ์ •ํ•˜๊ณ , ๋” ๋‚˜์€ UX๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.


๐Ÿงช ๋งํฌ ๋“œ๋ผํผ, ์ง€๊ธˆ ๋ฒ ํƒ€ ํ…Œ์ŠคํŠธ ์ค‘์ž…๋‹ˆ๋‹ค

๋งํฌ ๋“œ๋ผํผ๋Š” ๋‹จ์ˆœํ•œ ์ €์žฅ ํˆด์ด ์•„๋‹™๋‹ˆ๋‹ค.
์ •๋ฆฌํ•˜๊ณ , ์ˆ˜์ •ํ•˜๊ณ , ๋‹ค์‹œ ๊บผ๋‚ด๋ณด๊ฒŒ ๋งŒ๋“œ๋Š” ๋งํฌ ๊ด€๋ฆฌ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.
โ€ข ๐Ÿ”— ๋งํฌ ์‚ญ์ œ, ํด๋” ์ƒ์„ฑ/์ˆ˜์ • ๋“ฑ์„ ๋‚™๊ด€์ ์œผ๋กœ ์ฒ˜๋ฆฌ
โ€ข ๐Ÿง  ๋งํฌ ์˜๋ฏธ๋ฅผ ๊ธฐ์–ตํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฉ”๋ชจ ๊ธฐ๋Šฅ
โ€ข ๐ŸŒ ํด๋”๋ฅผ ์นœ๊ตฌ์—๊ฒŒ ๊ณต์œ  ๊ฐ€๋Šฅ
โ€ข โšก OG ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ž๋™ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

๐Ÿ‘‰ ๐Ÿ”— ๋งํฌ ๋“œ๋ผํผ ๋ฒ ํƒ€ ์ฒดํ—˜ํ•˜๋Ÿฌ ๊ฐ€๊ธฐ


๐Ÿ“ข ๋” ๋งŽ์€ ์†Œ์‹, ๋” ๋น ๋ฅด๊ฒŒ ๋ฐ›๊ณ  ์‹ถ๋‹ค๋ฉด?

์นด์นด์˜คํ†ก ์ฑ„๋„์„ ์ถ”๊ฐ€ํ•˜์‹œ๋ฉด
์—…๋ฐ์ดํŠธ ์†Œ์‹, ๊ธฐ๋Šฅ ๊ฟ€ํŒ, ์ด๋ฒคํŠธ ๋“ฑ์„ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ›์•„๋ณด์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‰ ๋งํฌ ๋“œ๋ผํผ ์นด์นด์˜คํ†ก ์ฑ„๋„ ์ถ”๊ฐ€ํ•˜๊ธฐ

profile
โ€œ๊ธฐ๋กํ•˜๋Š” ์Šต๊ด€์„ ๋„๊ตฌ๋กœ ๋งŒ๋“ค๋‹ค โ€” ๋‘ ๊ฐœ๋ฐœ์ž์˜ ๋งํฌ ๋“œ๋ผํผ ๊ตฌ์ถ•๊ธฐโ€

0๊ฐœ์˜ ๋Œ“๊ธ€