Skip to content

[nar info cache] Only fillNarInfoCache in perf-sensitive code path #1468

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

Merged
merged 3 commits into from
Sep 12, 2023

Conversation

savil
Copy link
Collaborator

@savil savil commented Sep 11, 2023

Summary

In this PR, we loosen the conditions under which devpkg.Package.IsInBinaryCache can be called. Internally, it checks an in-memory map for the status of the package in the binary cache. Previously, it was required that callers pre-compute this status by invoking FillNarInfoInCache. The motivation then was to ensure that we avoid a synchronous http request if IsInBinaryCache is called within a loop.

However, this condition and the fact that golang lacks async-await support resulted in a loosely coupled load-and-check-later design whereby calls to FillNarInfoInCache would be sprinkled in various parts of the codebase and then IsInBinaryCache checked in other parts. This loose-coupling made it hard to reason about why any particular FillNarInfoInCache was present.

The straw that broke the proverbial camel's back was the issue that #1463 was seeking to fix.

In this PR, we add a fallback http request inside IsInBinaryCache and remove all but one of the FillNarInfoInCache calls. Reason:

  1. The remaining callsite is in the hot codepath of calculating shellenv.
  2. The other callsites were in the codepaths of adding or installing packages. This is necessarily a slow operation, and so a small bit of synchrony is acceptable.

NOTE: this PR also re-enables Remove Nixpkgs feature flag.

How was it tested?

Copy link
Collaborator Author

savil commented Sep 11, 2023

Current dependencies on/for this PR:

This comment was auto-generated by Graphite.

@savil savil force-pushed the savil/simplify-fill-narinfo-cache branch from a3d5673 to 117341a Compare September 11, 2023 21:42
@savil savil force-pushed the savil/simplify-fill-narinfo-cache branch from 117341a to 019abe6 Compare September 11, 2023 21:59
@savil savil requested review from mikeland73 and ipince September 11, 2023 21:59
@@ -146,6 +163,7 @@ func (p *Package) fillNarInfoCache() error {
return nil
}

// isEligibleForBinaryCache returns true if the package is eligible for the binary cache.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, I'll improve this to be less self-referential.

@savil savil marked this pull request as ready for review September 11, 2023 22:19
Copy link
Contributor

@ipince ipince left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Contributor

@mikeland73 mikeland73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

)
if !statusExists {
// Fallback to synchronously filling the nar info cache
if err := p.fillNarInfoCache(); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For potentially simpler code. By definition, fillNarInfoCache() only returns a nil error if it succeeded. So you could do:

err := p.fillNarInfoCache()
return err == nil, err

Additionally, if fillNarInfoCache does

if isNarInfoInCache.status[p.Raw] {
  return nil
}

(no need to lock because we never remove from cache)

Then this entire function could be:

func IsInBinaryCache()
  if eligible, err := p.isEligibleForBinaryCache(); err != nil {
    return false, err
  } else if !eligible {
    return false, nil
  }
  err := p.fillNarInfoCache()
  return err == nil, err
}

(maybe rename fillNarInfoCache to fillNarInfoCacheIfNeeded for clarity)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this doesn't quite work for two reasons:

  1. fillNarInfoCache stores the boolean of whether the value is stored or not. A false value for a key in isNarInfoInCache does not indicate an error.

  2. Golang maps are not safe for concurrent reads and writes, so we will need a lock.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@savil you are correct on (1) but not on (2).

For (2), reading a true is only unsafe if we remove and/or change values. Since we should never remove/change values within a single command, I think this is safe and improves code readability quite a bit.

For (1), we can modify fillNarInfoCache() to return (status, error). The early return continues to be safe because if something is set to true it never changes.

An additional benefit of this approach is that we limit cache access to a single function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarified why (2) is correct in #1473 (comment)

@savil savil merged commit 0eb470d into main Sep 12, 2023
@savil savil deleted the savil/simplify-fill-narinfo-cache branch September 12, 2023 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants