Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hash: Transfer waiting list ownership to the objcore #3992

Closed
wants to merge 14 commits into from

Conversation

dridi
Copy link
Member

@dridi dridi commented Sep 28, 2023

The advantages of the objhead owning the waiting list are that we need less waiting list heads as they are shared by across multiple objcores, we get more frequent opportunities to rush waiting requests, and we avoid one expensive part of the lookup, which is to find an objhead from a request hash.

The downsides are that the requirement to always perform a new lookup, which increases contention on the objhead lock, and prevents expired but valid objects to be considered fresh. This is another way to describe the cache-control no-cache directive: a response considered fresh at fetch time, but immediately stale afterwards.

Having the waiting list on the objcore means that we reenter the cache lookup with the object candidate from the previous lookup, so we may short-circuit the more expensive objhead lookup when the objcore is compatible with the request rushing off the waiting list. The expensive objhead lookup from the request hash is still avoided, the objcore has a reference to the objhead.

This moves the infamous waiting list serialization phenomenon to the vary header match. Knowing that a rushed object is guaranteed to lead to a cache hit allows the rush policy to be applied wholesale, instead of exponentially. Only requests incompatible with the objcore vary header may reenter the waiting list, a scenario no different from the spurious rush wakeups when operating on the objhead.

The waiting list operations are still guarded by the objhead lock.

This is the first step towards implementing no-cache and private support at the header field granularity.

@dridi
Copy link
Member Author

dridi commented Oct 2, 2023

On Friday I took the original patch from this pull request and pushed it to the last mile. When I pushed the new patch series earlier I realized there was actually one more obvious thing to do (1f3b217).

Since this was established as a priority last week during the VDD, and since this is not a trivial change, I hope it can be triaged during bugwash today so the review effort can start sooner than later. I will try to join the bugwash but connectivity is terrible in my train.

@dridi
Copy link
Member Author

dridi commented Oct 2, 2023

Found more polish to apply after staring a little harder.

It has nothing to do with the hash lookup implementation, this is the
outcome of a cache lookup.
This is a prerequisite before moving waiting list ownership from objhead
to objcore.
Adding expired variants of miss and hitmiss outcomes will enable better
code organization, mainly the extraction of the objhead lookup step into
its own function.
This creates a separate objhead lookup after the hash lookup, and a
central location to process the objhead lookup result. This makes both
the lookup code and the lookup result processing code more compact and
easier to follow individually. In particular, the objhead lookup now
operates entirely under the objhead lock and has a much better signal
to noise ratio.
The advantages of the objhead owning the waiting list are that we need
less waiting list heads as they are shared across multiple objcores, we
get more frequent opportunities to rush waiting requests, and we avoid
one expensive part of the lookup, which is to find an objhead from a
request hash.

The downsides are the requirement to always perform a new lookup, which
increases contention on the objhead lock, and prevents expired but valid
objects to be considered fresh. This is another way to describe the
cache-control no-cache directive: a response considered fresh at fetch
time, but immediately stale afterwards.

Having the waiting list on the objcore means that we reenter the cache
lookup with the object candidate from the previous lookup, so we may
short-circuit the more expensive objhead lookup when the objcore is
compatible with the request rushing off the waiting list. The expensive
request hash lookup is still avoided, just like before, the objcore has
a reference to the objhead.

This creates a new first step in the lookup process:

- rush match (when coming from the waiting list)
- hash lookup (unless coming from the waiting list)
- objhead lookup (unless there was a rush hit)

This moves the infamous waiting list serialization phenomenon to the
vary header match. Knowing that a rushed object is guaranteed to lead to
a cache hit allows the rush policy to be applied wholesale, instead of
exponentially. Only requests incompatible with the objcore vary header
may reenter the waiting list, a scenario no different from the spurious
rush wakeups when operating on the objhead.

The waiting list operations are still guarded by the objhead lock. As it
stands currently, the exponential rush is indirectly systematic, since
the unusable objcore is rushed before the objhead lookup.

This is the first step towards implementing no-cache and private support
at the header field granularity.
Now that objcores own their waiting lists, they can no longer have
anything waiting for them at creation time.
This has the same effect, with slightly less ceremony.
The objcore is officially referenced by a request when it leaves its
waiting list during a rush. This allows migrating a waiting list from
one objcore to the next without the need to touch reference counters.

The hash_oc field will still have to be updated. This field must be
be kept to navigate from a request to its waiting list to implement
walking away from a waiting list.

Refs varnishcache#3835
This is very useful to manually verify the rush_exponent behavior.
After the migration of the waiting list to the objcore, the result was
that each request would rush more requests upon reembarking by dropping
the waited objcore reference before the objhead lookup.

The reference is now dropped after the objhead lookup, allowing for the
lookup outcome to be checked, either triggerring a complete rush when
the object is serviceable (modulus vary match) or moving the waiting
list to the next busy objcore.

This avoids the spurious wakeups caused by objhead rushes when waiting
lists were owned by objheads. This is however a missed opportunity when
there are multiple concurrent busy objcores. This could be alleviated
by linking busy objcores together but at this point this is already a
net improvement. The only way to get multiple concurrent busy objcores
on the same objhead would be through the hash_ignore_busy flag anyway.
Now that waiting lists are owned by objcores, the rush policy no longer
needs to leak everywhere. Either all the waiters are rushed at once for
cacheable objects, or according to rush_exponent.
@dridi
Copy link
Member Author

dridi commented Oct 3, 2023

In the absence of a bugwash yesterday I took the liberty of rearranging the patch series one more time.

@dridi dridi marked this pull request as draft November 27, 2023 14:18
@dridi
Copy link
Member Author

dridi commented Nov 27, 2023

Converted to draft, I want to revisit this series.

Bugwash: I can just push da96634 and acfab77.

@nigoroll
Copy link
Member

nigoroll commented Dec 5, 2023

Note from yesterday's bugwash: Dridi wants to take a slightly different approach and will come back. This is going to take some time.

@dridi
Copy link
Member Author

dridi commented Dec 20, 2023

Note from yesterday's bugwash: Dridi wants to take a slightly different approach and will come back. This is going to take some time.

I started and completed the new iteration yesterday, but didn't take time to submit #4032. I believe that this time I cut all the waiting list loose ends and managed much better trade offs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants