We have a client with very specific demands regarding scheduled publishing. The default “plus minus a minute and a half” result from a default setup is catastrophic for their business demands, so we are looking to improve this.
Can anyone recommend an approach to achieve better publishing timings? Something like within 5 seconds of the scheduled time would be acceptable.
What options do we have, and what would you recommend?
That’s a tough one. From what I remember, Umbraco uses a background job for handling scheduled content. The job runs every minute, so that explains the 1 minute less or more than the scheduled publish time. As far as I know, there is no way to change this interval with a setting.
The only thing I can think of, is to create your own background job and replace the one from Umbraco. Beware that the background job involves database calls, so you’ll be putting a lot more load on the database if you handle scheduled publishing every 5 seconds.
Sounds to me more like you should not rely on the background publishing.
If it’s a requirement to put something “on sale” at a specific time I would instead change my code to have a datetimeGoLive picker on the node and then hide / make it unavailable until the datetime is reached. ]
Take care with both Partialview caches, app caches and and Cloudflare / other cache in front of the website.
Misread that for a moment and though it was an issue with distributing the content cache to load balanced subscribers.. For which you can alther the timings
I wouldn’t recommend tying this business logic to Umbraco’s publishing at all.
Even if you can guarantee that Umbraco will start publishing in a 5 second window, you have no control over how long the publish itself will take. Site under a load? Some other background task happening at the same time? It could take longer.
I’ve done something similar to what @cheeseytoastie suggests before and it worked pretty well. We had blocks of time-sensitive content with a “rule picker” (we had more rules than just date) to determine when those blocks would be displayed or hidden.
You could have both the current and “next” versions of content published as separate nodes and determine when routing, perhaps in the contentfinder or with Cloudflare rules etc. which one gets served. Depending on how you structure content this could be a nicer experience for users - they can see the two versions clearly and even make edits to both independently.
Segments allow you to have multiple “versions” of a single document published at the same time, and you can then use any arbitrary rules (datetime, cookie value, querystring, whatever) to determine which segment is used.
Segments have a nice UI in the backoffice and are differentiated the same way as languages (i.e. you can choose which properties vary, there’s a nice dropdown to see different versions etc.).
Some thought would need to go into how you handle the segments, maybe some kind of rotation of now/next could be automated?
In this case, there is no document published at all, so we are going from 0 to 100 basically.
Imagine the following rules:
There can be no url showing the content at 8:59
At 9:00 there is a press release going out, and people are awaiting this release. At this point the url is distributed not only in the press release but other venues and by then the url must work.
The publishing time itself should not be an issue for this case, but it’s a good thing to keep in mind.
Best would be to be able to schedule it like 5 seconds before your target time and have it start publishing by then.
I’ve never tried Hangfire, but maybe that could be the most robust solution? Paired with a controller action to publish the relevant document via guid or similar.
I still think that if it’s THAT critical, maybe the content should be published beforehand, but have something like a middleware that actively blocks the request until it’s time maybe? It’s probably not easy to account for all the case you need to handle, but it’s an idea.
Yes exactly something like that. I think the indexing and caching is indeed a possible issue, that’s what I meant with that it’s probably not easy to account for all cases you need to handle. It depends a little how strict it is that content cannot be found before the deadline. Otherwise you could publish 5 minutes in advance while the middleware will keep the page from getting rendered, with a small risk that someone might find a seach result with the page before the deadline (even though the page itself won’t open).
Another idea we had to make this a bit more user friendly was to listen to the event of scheduling a publish, and at that point alter the publishdate minus a few minutes and also add this more “specific” field, updated with the planned publish date.
But there doesn’t seem to be a notification that happens during the act of setting up a schedule though?
This is exactly how I handle scheduled blog posts, or items with an expiry date. Have the page specific RenderController check if the blog post should be visible or not. If not return a 404, if it is available return the blog post. The only thing you need to take into account is that the page should always be visible in the Umbraco preview mode, otherwise editors can’t preview a blog post that’s scheduled at a later date. The example code uses some custom services and helpers, but you get the idea…
public class BlogPageController : RenderController
{
private readonly SiteService _siteService;
private readonly UmbracoHelperService _umbracoHelperService;
public BlogPageController(
ILogger<BlogPageController> logger,
ICompositeViewEngine compositeViewEngine,
IUmbracoContextAccessor umbracoContextAccessor,
SiteService siteService,
UmbracoHelperService umbracoHelperService) : base(logger, compositeViewEngine, umbracoContextAccessor)
{
_siteService = siteService;
_umbracoHelperService = umbracoHelperService;
}
public override IActionResult Index()
{
var blogPage = (CurrentPage as BlogPage)!;
var isActive = ContentHelper.IsActive(blogPage.PublicationDate, blogPage.ExpirationDate);
// Checks for "UMB_PREVIEW" cookie
if (_umbracoHelperService.InPreviewMode() == false && isActive == false)
{
var notFoundPage = _siteService.GetNotFoundPage();
if (notFoundPage != null)
{
HttpContext.Response.StatusCode = 404;
return View("~/Views/NotFoundPage.cshtml", notFoundPage);
}
}
return CurrentTemplate(blogPage);
}
}