How to configure the retention time of job?

Hi Sergey,

May I know if it’s possbile to make the retention time longer? seem like the current job retention time is about 1 day, it would be great if we can configure it to a longer period say a week? coz, it’s more practical in production environment that we need to keep track the job status in longer time period, particularly important when investigating the failed job.

Thanks again for bring this great project to us!

Alex

1 Like

First of all, retention, or expiration time is setting up only for successful and deleted jobs. Other job states, including the failed state, don’t have any expiration time. That is why your failed jobs will be kept infinitely before you fix/retry or delete them manually.

But if you really want to set up retention time for succeeded or deleted jobs, you can add the following job filter (they are like action filters in ASP.NET MVC):

public class ProlongExpirationTimeAttribute : JobFilterAttribute, IApplyStateFilter
{
    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        context.JobExpirationTimeout = TimeSpan.FromDays(7);
    }
}

And apply this filter globally…

GlobalJobFilters.Filters.Add(new ProlongExpirationTimeAttribute());

… or locally, by decorating the method or its class:

[ProlongExpirationTime]
public void SomeMethod()
{
    // Implementation
}

In the recent versions of Hangfire it is also possible to specify the expiration globally by using the WithJobExpirationTimeout method just after the UseXXXStorage (where XXX is the storage you are using) and pass the required timeout as an argument.

Configuration
    .UseXXXStorage()
    .WithJobExpirationTimeout(TimeSpan.FromDays(4))
9 Likes

Thanks for this, Sergey!

Is there any problem leaving the OnStateUnapplied method like this? I don’t have an implementation yet…

public void OnStateUnapplied(ApplyStateContext context, Hangfire.Storage.IWriteOnlyTransaction transaction)
{
    throw new NotImplementedException();
}

Thanks!

You are welcome! In this case every background job will end up with an exception, I doubt you want it.

Hope will write more about extensibility in the near future.

Heh, nope don’t want that! I’ll just leave the method empty for now.

Thanks for the quick reply :smile:

Hi,
How can i apply this attribute using Asp.net Web Application?

Thank you

Hello,
The @odinserj helped me.

I solved my issue, I did some changes into the class

public class ProlongExpirationTimeAttribute : JobFilterAttribute, IApplyStateFilter
{
    public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        context.JobExpirationTimeout = TimeSpan.FromDays(7);
    }

    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        context.JobExpirationTimeout = TimeSpan.FromDays(7);
    }
}

Now, my job retention time is 7 days.

Thank you so much.
Denis

@odinserj When does the expiration manager kick in? is it based on an interval or is it only at application startup?

1 Like

If you look at the SQLServerStorage class in GitHub you can see the default settings.

public SqlServerStorageOptions()
{
	TransactionIsolationLevel = null;
	QueuePollInterval = TimeSpan.FromSeconds(15);
	InvisibilityTimeout = TimeSpan.FromMinutes(30);
	JobExpirationCheckInterval = TimeSpan.FromHours(1);
	CountersAggregateInterval = TimeSpan.FromMinutes(5);
	PrepareSchemaIfNecessary = true;
	DashboardJobListLimit = 50000;
	_schemaName = Constants.DefaultSchema;
	TransactionTimeout = TimeSpan.FromMinutes(1);
}

For one of my projects I wanted to expire some of my manually enqueued jobs after 1 minute. To override the above default settings I used the following in my Hangfire initialization code.

GlobalConfiguration.Configuration.UseSqlServerStorage("DbConnectionString",
new Hangfire.SqlServer.SqlServerStorageOptions 
{
	JobExpirationCheckInterval = TimeSpan.FromMinutes(1)
});

Hey, did you get an answer to this, I am interested too.

@odinserj Does this need to be applied at the server only or both client and server? I get confused sometimes as to which methods, properties, or settings should be invoked on the client vs the server since some are set through Global configuration and some are set through the Owin or .NetCore extension methods.

Sometimes documentations specify which but I wasn’t sure if there’s like a common rule.

Thanks for your help!

I’m trying to implement this, I have the following
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
context.JobExpirationTimeout = TimeSpan.FromSeconds(10);
}
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
context.JobExpirationTimeout = TimeSpan.FromSeconds(10);
}

But it only erases the tasks of the succedded page if I restart The process, any idea? thanks

Job expiration runs every 30 minutes by default (for SqlServerStorage, others may vary). So your jobs theoretically should expire in 10 seconds, but it will be actually done only half an hour later.

You may configure JobExpirationCheckInterval in storage options. But running it too often may create a lot of load on storage database, so beware.

1 Like

This is one of the things I love about Mongo (though I am not sure come to think of it if Sergey uses it in the Mongo storage plugin for Hangfire), TTL indexes.

You can literally create an index on a collection (table for you SQL lubbers :slight_smile: …) that will do server side expiration of documents (records for you SQL lubbers :wink: …)

See Mongo Docs wiki

(edit - solved, see below)

I’m trying to use this attribute at the method level with v1.6.20, but it doesn’t seem to be honoured.
Can anyone see something I’m doing wrong, or has anyone else seen the same?

Per http://docs.hangfire.io/en/latest/tutorials/send-email.html?highlight=jobfilterattribute#logging I’ve added the attribute against the method being called, rather than the caller.

public class BackOfficeBusinessService : IBackOfficeBusinessService
{
    [HangfireExpirationTime(hours: 2)]
    public async Task UserLoginJobs(int userId)
    {
       // do stuff
    }
}

Which is called by the login controller:

public async Task<IHttpActionResult> Login(LoginCommand command)
{
    var response = await _loginService.Login(command);
    Hangfire.BackgroundJob.Enqueue<IBackOfficeBusinessService>(service =>
                service.UserLoginJobs(response.UserId)
    );
    return Ok(response);
}

Solution ----------------------------
Per https://stackoverflow.com/a/50224619/154170, as I am using DI, putting the attribute on the interface method works instead:

public interface IBackOfficeBusinessService
{
    [HangfireExpirationTime(hours: 2)]
    Task UserLoginJobs(int userId);
}

It’s not working for me. Are you sure this works?

I don’t understand the part “Apply Filter Globally”. Where should I apply it? I’m using Web Application with Angular and when I apply it into startup.cs, I can’t get my job to be done. I can still generate new job but they won’t run when it’s time.

If you want it to applied globally, yes. You will add it to the GlobalJobFilters on the startup. Ex:

GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3, LogEvents = false });

I recently started playing around with Hangfire and am trying to use the new way of changing the Job Expiration Timeout by using WithJobExpirationTimeout as mentioned at the bottom of this page ([Background Methods — Hangfire Documentation]). However, I have been unable to get it to work; it still says it will expire in 1 day. I am using Hangfire 1.8.6 with .Net 8. Below is my code snippet. Am I doing something wrong?

GlobalConfiguration.Configuration
      .UseSqlServerStorage(builder.Configuration.GetConnectionString("Hangfire"), new SqlServerStorageOptions
      {
          CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
          SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
          QueuePollInterval = TimeSpan.Zero,
          UseRecommendedIsolationLevel = true,
          DisableGlobalLocks = true // Migration to Schema 7 is required
      })
      .WithJobExpirationTimeout(TimeSpan.FromDays(7));

I can get it to work if I use the old way by setting up global job filter attribute as mentioned in posts above.

Thanks for the help.