Skip to content

Conversation

jgallagher
Copy link
Contributor

This adds a background task that periodically attempts to read the latest inventory collection from the DB. It first does a query for just the latest collection ID, then only does the full set of queries to load the entire collection if the ID has changed from the last one it read. Therefore, I set the period pretty aggressively (15 seconds), which is what #5296 suggested. (Neither this PR nor the followup do the rest of #5296; i.e., changing how the inventory_collector task works.)

As of this PR, we only introduce the task and use it in tests. The followup PR changes all the other background tasks that currently read collections from the DB to read from this task's watch channel instead.

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

neat!

Comment on lines 1996 to 2001
println!(
" loaded latest inventory collection as of {}: \
collection {collection_id}, taken at {}",
humantime::format_rfc3339_millis(time_loaded.into()),
humantime::format_rfc3339_millis(time_started.into()),
);
Copy link
Member

Choose a reason for hiding this comment

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

turbo nit: looking at the expectorate output, this ends up formatting a pretty long line, i might consider splitting it so it's less likely to wrap in smallish terminals?

maybe something like:

Suggested change
println!(
" loaded latest inventory collection as of {}: \
collection {collection_id}, taken at {}",
humantime::format_rfc3339_millis(time_loaded.into()),
humantime::format_rfc3339_millis(time_started.into()),
);
println!(
" loaded latest inventory collection as of {}:\n\
collection {collection_id}\n\
taken at {}",
humantime::format_rfc3339_millis(time_loaded.into()),
humantime::format_rfc3339_millis(time_started.into()),
);

Comment on lines +116 to +120
return InventoryLoadStatus::Loaded {
collection_id: old_id,
time_started: old_time_started,
time_loaded,
};
Copy link
Member

Choose a reason for hiding this comment

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

nitpicky: it could be worth including something in the status message that explicitly indicates whether the collection was newly loaded on this activation or not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I originally had it that way, but I changed it for a maybe-bad reason: I think it makes the omdb expectorate test flaky (in that we have a race between omdb seeing the first activation that loaded a collection vs seeing a subsequent activation that didn't).

Copy link
Member

Choose a reason for hiding this comment

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

ah, yeah, that's fair. perhaps it could be redacted, but that might be more work than it's worth.


pub struct InventoryLoader {
datastore: Arc<DataStore>,
tx: watch::Sender<Option<Arc<Collection>>>,
Copy link
Member

Choose a reason for hiding this comment

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

note that a watch::Receiver is basically just "an Arc plus some other stuff", so depending on usage patterns, it may not be necessary to have two layers of reference counting. however, this does let us hold onto prior inventories if they're still being used when a new one is loaded, so this may be desirable...

Copy link
Contributor Author

@jgallagher jgallagher Oct 6, 2025

Choose a reason for hiding this comment

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

however, this does let us hold onto prior inventories if they're still being used when a new one is loaded, so this may be desirable...

Yeah, this is exactly what I want - a Collection is pretty big, so cloning it is far from free, and I don't want the consumers of this channel to keep it locked while they act on it. This more visible on #9149; e.g.,

        // Get the inventory most recently seen by the inventory loader
        // background task. We clone the Arc to avoid keeping the channel locked
        // for the rest of our execution.
        let Some(collection) =
            self.rx_inventory.borrow_and_update().as_ref().map(Arc::clone)
        else {
            // ...
        };

Copy link
Member

Choose a reason for hiding this comment

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

yup, makes sense! perhaps worth commenting?

@jgallagher jgallagher merged commit 4449f01 into main Oct 7, 2025
17 checks passed
@jgallagher jgallagher deleted the john/inventory-fetch-bgtask branch October 7, 2025 19:27
jgallagher added a commit that referenced this pull request Oct 8, 2025
…PR 2/2) (#9149)

(This is the second half of #9148.)

As of this PR, all (I believe!) the background tasks that want to act on
the latest inventory collection now act on the latest inventory
collection loaded by the `inventory_loader` bg task. That task runs
frequently.

Fixes #8882. It's still possible two Nexuses could duel if one is acting
on a newer inventory collection, but that's always true. The window for
that should now be ~15 seconds, instead of waiting until the next time
_our own Nexus_ collects inventory.

I did not change other places in Nexus where we're reading inventory
outside of background tasks. I think it would probably make sense to
audit for those and make them read from this watch channel too? If that
sounds right I can file an issue.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants