backup_worker.mjs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import Queue from 'bull'
  2. import logger from '@overleaf/logger'
  3. import config from 'config'
  4. import metrics from '@overleaf/metrics'
  5. import {
  6. backupProject,
  7. initializeProjects,
  8. configureBackup,
  9. } from './backup.mjs'
  10. const CONCURRENCY = 10
  11. const redisOptions = config.get('redis.queue')
  12. const TIME_BUCKETS = [10, 100, 500, 1000, 5000, 10000, 30000, 60000]
  13. // Configure backup settings to match worker concurrency
  14. configureBackup({ concurrency: 50, batchConcurrency: 3 })
  15. // Create a Bull queue named 'backup'
  16. const backupQueue = new Queue('backup', {
  17. redis: redisOptions,
  18. settings: {
  19. lockDuration: 15 * 60 * 1000, // 15 minutes
  20. lockRenewTime: 60 * 1000, // 1 minute
  21. maxStalledCount: 0, // mark stalled jobs as failed
  22. },
  23. })
  24. // Log queue events
  25. backupQueue.on('active', job => {
  26. logger.info({ job }, 'job is now active')
  27. })
  28. backupQueue.on('completed', (job, result) => {
  29. metrics.inc('backup_worker_job', 1, { status: 'completed' })
  30. logger.info({ job, result }, 'job completed')
  31. })
  32. backupQueue.on('failed', (job, err) => {
  33. metrics.inc('backup_worker_job', 1, { status: 'failed' })
  34. logger.error({ job, err }, 'job failed')
  35. })
  36. backupQueue.on('waiting', jobId => {
  37. logger.info({ jobId }, 'job is waiting')
  38. })
  39. backupQueue.on('error', error => {
  40. logger.error({ error }, 'queue error')
  41. })
  42. backupQueue.on('stalled', job => {
  43. logger.error({ job }, 'job has stalled')
  44. })
  45. backupQueue.on('lock-extension-failed', (job, err) => {
  46. logger.error({ job, err }, 'lock extension failed')
  47. })
  48. backupQueue.on('paused', () => {
  49. logger.info('queue paused')
  50. })
  51. backupQueue.on('resumed', () => {
  52. logger.info('queue resumed')
  53. })
  54. // Process jobs
  55. backupQueue.process(CONCURRENCY, async job => {
  56. const { projectId, startDate, endDate } = job.data
  57. if (projectId) {
  58. return await runBackup(projectId)
  59. } else if (startDate && endDate) {
  60. return await runInit(startDate, endDate)
  61. } else {
  62. throw new Error('invalid job data')
  63. }
  64. })
  65. async function runBackup(projectId) {
  66. const timer = new metrics.Timer(
  67. 'backup_worker_job_duration',
  68. 1,
  69. {},
  70. TIME_BUCKETS
  71. )
  72. try {
  73. logger.info({ projectId }, 'processing backup for project')
  74. const { errors, completed } = await backupProject(projectId, {})
  75. metrics.inc('backup_worker_project', completed - errors, {
  76. status: 'success',
  77. })
  78. metrics.inc('backup_worker_project', errors, { status: 'failed' })
  79. timer.done()
  80. return `backup completed ${projectId} (${errors} failed in ${completed} projects)`
  81. } catch (err) {
  82. logger.error({ projectId, err }, 'backup failed')
  83. throw err // Re-throw to mark job as failed
  84. }
  85. }
  86. async function runInit(startDate, endDate) {
  87. try {
  88. logger.info({ startDate, endDate }, 'initializing projects')
  89. await initializeProjects({ 'start-date': startDate, 'end-date': endDate })
  90. return `initialization completed ${startDate} - ${endDate}`
  91. } catch (err) {
  92. logger.error({ startDate, endDate, err }, 'initialization failed')
  93. throw err
  94. }
  95. }
  96. export async function drainQueue() {
  97. logger.info({ queue: backupQueue.name }, 'pausing queue')
  98. await backupQueue.pause(true) // pause this worker and wait for jobs to finish
  99. logger.info({ queue: backupQueue.name }, 'closing queue')
  100. await backupQueue.close()
  101. }
  102. export async function healthCheck() {
  103. const count = await backupQueue.count()
  104. metrics.gauge('backup_worker_queue_length', count)
  105. }