Skip to content

Make GeoIp downloader multi-project aware #128282

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

Open
wants to merge 48 commits into
base: main
Choose a base branch
from

Conversation

samxbr
Copy link
Contributor

@samxbr samxbr commented May 22, 2025

This change makes the GeoIp persistent task executor/downloader multi-project aware.

  • the database downloader persistent task will be at the project level, meaning there will be a downloader instance per project
  • persistent task id is prefixed with project id, namely <project-id>/geoip-downloader for cluster in MP mode

To keep the size of PR review friendly, this PR only focus on the downloading part of GeoIP database, there will be more changes coming in separate PRs to make GeoIP multi-project aware in general.

@samxbr samxbr requested a review from Copilot June 8, 2025 09:38
Copilot

This comment was marked as outdated.

samxbr and others added 2 commits June 8, 2025 17:40
…oip/GeoIpDownloader.java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@samxbr
Copy link
Contributor Author

samxbr commented Jun 10, 2025

  • Can you also update the title and/or final squashed commit message to be a bit more specific?

Updated the title, and will for sure squash the commits before merging

  • Did you try running the GeoIP YAML tests in MP mode too? Or do those require some more work?

The GeoIP yaml tests requires more work in MP, which is loading the databases from system index to ingest node. To limit the size of this PR, I will include that part in another PR

@samxbr samxbr requested a review from nielsbauman June 17, 2025 16:10
if (clusterService.state().nodes().isLocalNodeElectedMaster() == false) {
// we should only start/stop task from single node, master is the best as it will go through it anyway
return;
}
if (enabled) {
startTask(() -> {});
startTask(projectResolver.getProjectId(), () -> {});
Copy link
Contributor

Choose a reason for hiding this comment

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

This only works because the project resolver currently still returns a fallback project ID (which will be removed in the future). This will need to be updated in the future anyway as well (when these settings are made project-aware), but I'd still prefer that we just hard-code ProjectId.DEFAULT instead. That way we at least don't rely on temporary behavior. The same goes for the methods/settings below.

GeoIpDownloader currentDownloader = getCurrentTask();
if (currentDownloader != null) {
currentDownloader.requestReschedule();
for (var projectMetadataEntry : event.state().metadata().projects().entrySet()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer that we put the annotation here as well. That way we're sure it won't be forgotten.

Comment on lines 234 to 237
boolean taskIsBootstrapped = taskIsBootstrappedByProject.computeIfAbsent(projectId, k -> false);
if (taskIsBootstrapped != true) {
taskIsBootstrappedByProject.put(projectId, true);
this.taskIsBootstrappedByProject.computeIfAbsent(projectId, k -> hasAtLeastOneGeoipProcessor(projectMetadata));
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I missed that taskIsBootstrappedByProject is used async in startTask and stopTask. The current code isn't 100% airtight. I was checking on ConcurrentHashMap if there's anything we can use, but I don't think so. ConcurrentHashMap#replace is close, but it doesn't account for unset/null values... So, I think we have to go back to the AtomicBoolean inside the map again, sorry about that. I think the other maps are OK like this.

Comment on lines 259 to 262
var atLeastOneGeoipProcessor = atLeastOneGeoipProcessorByProject.computeIfAbsent(projectId, k -> false);
boolean newAtLeastOneGeoipProcessor = hasAtLeastOneGeoipProcessor(projectMetadata);

if (newAtLeastOneGeoipProcessor && atLeastOneGeoipProcessor == false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
var atLeastOneGeoipProcessor = atLeastOneGeoipProcessorByProject.computeIfAbsent(projectId, k -> false);
boolean newAtLeastOneGeoipProcessor = hasAtLeastOneGeoipProcessor(projectMetadata);
if (newAtLeastOneGeoipProcessor && atLeastOneGeoipProcessor == false) {
var atLeastOneGeoipProcessor = atLeastOneGeoipProcessorByProject.getOrDefault(projectId, false);
if (atLeastOneGeoipProcessor == false && hasAtLeastOneGeoipProcessor(projectMetadata)) {

and move the put below to inside the if-block.

  • doing getOrDefault instead of computIfAbsent avoids the redundant new value
  • performing the (relatively expensive check) as the second condition allows us to skip it if we already set this value to true once before (as it'll never get set to false, we only remove the entry)
  • we don't need to put false on every run

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 don't think we can move the put inside the if block or avoid hasAtLeastOneGeoipProcessor . It's possible to remove a Geoip processor, in which case we need to set the value from true to false. It's also easier to reason about the logic this way.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yeah you're right; I misread the old code. I think it's still worth doing getOrDefault instead of computeIfAbsent and I'd wrap the put inside a if (atLeastOneGeoipProcessor != newAtLeastOneGeoipProcessor), to save some unnecessary updates.

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'd prefer still using computeIfAbsent, there's a value always exists in the map for each project, it's just one put for every project, I don't think it's a huge problem. I added the conditional put though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what the value is of doing the compute? It's an extra put on the map without any value.

.putProjectMetadata(ProjectMetadata.builder(project2).build())
.build();
ClusterChangedEvent event = new ClusterChangedEvent("_na_", newState, originalState);
assertTrue(event.customMetadataChanged(project2.id(), IndexGraveyard.TYPE));
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this IndexGraveyard line a leftover, or did you put it there intentionally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's intentional, IndexGraveyard is always added when a new project is created, this checks that IndexGraveyard is "changed".

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, could you put a comment here and below to mention that?

@samxbr samxbr requested a review from nielsbauman June 19, 2025 14:44
Comment on lines 234 to 237
this.taskIsBootstrappedByProject.computeIfAbsent(
projectId,
k -> new AtomicBoolean(hasAtLeastOneGeoipProcessor(projectMetadata))
);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think these lines are correct. We're already setting taskIsBootstrapped above, right? Or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

Comment on lines 259 to 262
var atLeastOneGeoipProcessor = atLeastOneGeoipProcessorByProject.computeIfAbsent(projectId, k -> false);
boolean newAtLeastOneGeoipProcessor = hasAtLeastOneGeoipProcessor(projectMetadata);

if (newAtLeastOneGeoipProcessor && atLeastOneGeoipProcessor == false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yeah you're right; I misread the old code. I think it's still worth doing getOrDefault instead of computeIfAbsent and I'd wrap the put inside a if (atLeastOneGeoipProcessor != newAtLeastOneGeoipProcessor), to save some unnecessary updates.

.putProjectMetadata(ProjectMetadata.builder(project2).build())
.build();
ClusterChangedEvent event = new ClusterChangedEvent("_na_", newState, originalState);
assertTrue(event.customMetadataChanged(project2.id(), IndexGraveyard.TYPE));
Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, could you put a comment here and below to mention that?

@samxbr samxbr requested a review from nielsbauman June 20, 2025 04:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Data Management/Ingest Node Execution or management of Ingest Pipelines including GeoIP >non-issue Team:Data Management Meta label for data/management team v9.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants