prebid / Prebid.js

Setup and manage header bidding advertising partners without writing code or confusing line items. Prebid.js is open source and free.
https://docs.prebid.org
Apache License 2.0
1.28k stars 2.05k forks source link

Interest on Yielding on the Main Thread to optimize UX of sites using prebid.js? #10062

Open gilbertococchi opened 1 year ago

gilbertococchi commented 1 year ago

Type of issue

Feature Request

Description

Chrome recently announced that INP will become a Core Web Vitals Metric. To optimize User Experience as INP suggest it's a good approach for 1P and 3P developers to Yield on the Main thread and reduce Long Tasks when possible.

https://web.dev/optimize-long-tasks/ is a great article that covers the different methodologies to Yield on the Main Thread.

Some of these methods are available (like scheduler.postTask) and some others will be shortly available via origin Trival (scheduler.yield).

It would be positive if prebid.js as well as other 3Ps libraries used by websites would Yield on the Main Thread, so in case of User Interaction, the Browser will be able to Render the Next Frame.

In the Screenshot you can see the Long Task generated by the prebid.js Example container taken from this link

prebid

Would there be any interest from prebid.js Library owners to consider some of this approach in order to allow Prebid.js to work as usual while reducing it's Main Thread blocking time?

happy to chat more about this. Gilberto

dgirardi commented 1 year ago

Prebid is actually intentionally hoarding the main thread. See this discussion for reference. I lost that argument at the time but I am still of the opinion that we should give up control as you suggest, so maybe it's time to revisit it again.

mmocny commented 1 year ago

Thanks for that extra context. Here is one comment that I think summarizes a long thread well:

i don't think a if (timeout === 0) callback() else setTimeout(callback, timeout) style is a good practice in general but i think it makes perfect sense in our case. the whole concept of header bidding is to hold an auction in the browser before the page loads with the goal of rendering with the page, and anytime we defer execution we work against that goal.

I think this makes sense to me: if there is explicit intent to block rendering on the results of the ad auction, to prevent flash of content without ads -> with ads, that seems reasonable.

However, once the page is already rendered, and already looks interactive, I think this constraint should not be upheld.

I dont know enough about prebid.js to know how often pages uses it in the latter use case -- but it seems to me that if it is a non-trivial amount, that you should support both modes?

dgirardi commented 1 year ago

It's relatively trivial for a website to wait for Prebid ads to be ready before displaying their widgets if that's the concern, so even with that I think playing nice (meaning allowing others to use the main thread) should be the default. I may have a skewed perception because - coming from other web UX work - having the browser render loop freeze is pretty much the worst sin imaginable in my book.

It's possible though that it's worth the tradeoff if it means even a tiny increase in ad revenue. I think we should try some A/B testing to see if that's even there; I am hopeful that this is just a case of premature optimization and we can avoid supporting both modes long term.

patmmccann commented 1 year ago

Discussion in committee today indicates an openness to exploring this idea further, particularly in the case of waiting for an rtd or user module, or after bid requests have been fired.

snapwich commented 1 year ago

It's possible though that it's worth the tradeoff if it means even a tiny increase in ad revenue.

i don't even really consider my argument from an ad revenue perspective. from a ux perspective i don't think it's desirable to have the page load and then have ads start popping in after page load which is why i think it's very important to kick off the auction ASAP and then naturally defer while we're waiting for responses. ideally we'd have ads ready to go as the page renders.

there's almost certainly work that could be done to make sure we're only doing important things related to starting the auction before we send the requests. and if we're blocking the main thread after ads have rendered, for instance on events from user clicks or something, i think that is bad.

if it were possible to prepare the auction in a webworker i think that could also be a desirable approach rather than deferring the start of the auction until later.

edit - also as you said, @dgirardi, i think a/b testing some of these assumptions is a good idea.

gilbertococchi commented 1 year ago

Hello everyone, thanks for the great feedback.

Just to clarify the suggestion, I don't think it should be necessary/suggested to delay any network requests triggered by prebid at all via deferring the script etc.

The suggestion I've drafted at the beginning aims more to "split the JS tasks" which may not result in delaying them in most cases.

If there might be a User Interaction during the Prebid.js JS related Main Thread blocking time currently users may not be able to see any result of the user interaction until the Task has finished (that's what INP highlights).

By splitting the tasks the effect would simply be that the Browser would be able to respond to the User Input showing the feedback as next Paint (that could be as simple as button/link highlight background).

It should not necessarily cause delay in the logics handled by prebid in anyway.

In the article I've linked (https://web.dev/optimize-long-tasks/) it's possible to find all the different methods to yield depending on the Browser support and their pros and cons.

The latest we are proposing (on Chromium) is called Scheduler.yield and it will be soon availble via Origin Trial (https://developer.chrome.com/origintrials/#/view_trial/836543630784069633) with Chrome 115 (hitting Stable by Mid July). It's also possible for 3Ps like prebid to singup to Origin Trials and experiment this method.

The benefit of Scheduler.yield is that it assures Yield with Continuation which sounds like the best solution considering the prebid requirements (not to delay Ads) in my opinion.

More info about Scheduler Yield available here: https://github.com/WICG/scheduling-apis/blob/main/explainers/yield-and-continuation.md

ethangardner commented 1 year ago

@gilbertococchi - I agree that this would be a good thing. In looking at the long tasks that run on sites that I work on, most of the long tasks and total blocking time is from ads. If there was more management of the long tasks using siteTimeout or one of the newer mechanisms, that would help reduce clogging up the main thread.

Since prebid is (thankfully) a versioned library this would be a great addition to have in future releases.

In this presentation by DotDash Meredith, they mention that ads are the slowest part of their site. That's going to be a given due to the request chain that has to happen in header bidding, but it would be wonderful if there was some yielding that happened to keep the UI snappy while the ad fulfillment process happens.

gilbertococchi commented 10 months ago

Hello everyone, from my understanding it seems like Prebid allows to register for Callback events to be triggered by different Ad Network integrations via using the pbjs.que.push or similar method.

This is a mechanism that works really well to invoke callback from the Prebid vs another AdNetwork etc but it also means all of this runs synchronously on the Main Thread.

If Prebid would execute all the que callback as Promises as async methods Publishers would be in a position to Yield if they want to, maybe using one of the new methods such as scheduler.postTask or scheduler.yield() that ensure Script continuation limiting the delay introduced by yielding.

Doing so would improve the Publishers perception of Prebid as JS Blocking Time contributor, it would highlight what Ad Network if any is actually keeping the JS Main Thread busier than others (by slipping Tasks Attribution improves) and reducing INP potential impact by all these JS Tasks.

nanek commented 9 months ago

As a developer for a publisher, we also have this need to break up the long tasks Prebid.js creates as described in this issue. As noted, this negatively impacts the INP score for the website. I looked at attempting this a few years back, but it exceeded my knowledge of Prebid internals. I'm happy to provide any testing or a/b testing if an implementation was created.

gilbertococchi commented 9 months ago

Thanks @nanek for chiming in.

I am starting to develop a theory that the best way to solve this could be to provide some optional method to run an async/await led queue of JS Promises instead of single functions in chain.

By doing that Publishers may be able to Yield with Continuation, effectively using the best methods available such as scheduler.postTask and/or scheudler.yield, reducing INP without delaying Ad Requests in a noticeable way.

That would require Prebid to allow to have another queue running via promises that should be resolved as an optional implementation method.

@dgirardi do you know if anything similar has ever being discussed in the past?

dgirardi commented 9 months ago

I believe it's the same point of contention brought up in the first reply to the OP - Prebid does not want to delay the first auction, which means either never giving up the main thread or possibly moving all computation into a web worker (although I suspect that normal deferrals are likely to be faster than communicating with a worker).

For a quick test, it's relatively easy to "turn off" a lot of the main thread hoarding by making GreedyPromise an alias of Promise. It's likely that will break some things - I know a lot of unit tests are relying on the current behavior - but it may be enough to see what it does to page performance metrics.

dgirardi commented 9 months ago

I'll add that in my view periodically giving up control is a good idea (and, in fact, always better UX), but I'm in the minority so it would have to be optional which is unfortunately no small task - but still worth doing.

nanek commented 9 months ago

When I investigated this before in https://github.com/prebid/Prebid.js/issues/5054, the majority of the time was in callBids for my site with 10+ bidders and multiple ad units on the page. Breaking up callBids alone split up the long task into many smaller tasks. However I didn't proceed with any additional testing at the time.

gilbertococchi commented 9 months ago

Thanks @dgirardi for your feedback about the topic, it really makes sense!

About your point @nanek on https://github.com/prebid/Prebid.js/issues/5054 I agree that the callBids could be an ideal candidate for yielding but perhaps I would have not suggested to use setTimeout for this specific purpose as there are some risks of delaying the Auction request too much if there are other timers being running.

I believe that once scheduler.yield() will become available in Chrome Stable the risks to delaying bidding will be eliminated as the method would allow to Yield while ensuring continuation (no other scripts could chime in in the splitted task). Read more about the method here: https://github.com/WICG/scheduling-apis/blob/main/explainers/yield-and-continuation.md

@shaseley FYI as he's the engineering working on this method.

Deimos01 commented 6 months ago

Hi @gilbertococchi,

Do you have an ETA for this method to be available in Chrome Stable ? Origin Trial ended 6 days ago : https://developer.chrome.com/origintrials/#/view_trial/836543630784069633

As you know FID was removed as a Core Web Vital this month, replaced with INP so I would like to yield callBids in order to speed up INP metric.

gilbertococchi commented 5 months ago

Hi Laurent, sorry for late reply here, I missed this notification somehow.

We don't have an ETA for the Scheduler.yield() Stable launch just yet but we are working to ship it as soon as possible as the Standardization process can allow.

@mmocny FYI

Having said that I am not sure if the scheduler.yield() method is possible to use in the callBids internal in Prebid.

I don't know Prebid but my naive understanding is that everything is currently running as synchronous and not promised based.

If there would be an option to implement some of this processes to be promised based and using async/await I believe that scheduler.yield could be a good fit to yield without negatively impacting Ads KPIs

You can find a recent example on web.dev that talks about how this is possible without damaging Ads metrics here: https://web.dev/case-studies/taboola-inp when they mentioned that "business metrics, such as Ad Click-through Rate and Revenue per 1,000 impressions (RPM), were not negatively impacted".