-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Sync single external service #13483
Sync single external service #13483
Conversation
Tests are still failing
Also removes a test case that tests a sync of muktiple external services as we no longer support that.
Code now compiles but a lot of tests still fail. Added a cleanup function that needs to be called when we stop a worker to unregister prometheus metrics so that it doesn't panic when we start another worker in a subsequent test.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
If a job is processing when repo-updater dies that job would never complete. We would then not requeue the associated external service for syncing. To be safe we delete non locked processing rows on startup.
This will cause a sync to be triggered ASAP for the saved external service. On save, we trigger a call to repo-updater which causes us to enqueue any pending sync jobs. As we've just upated next_sync_at the job for the newly saved repo will be queued.
We no longer trigger a full sync but instead trigger enqueueing pending sync jobs.
We now have a trigger that does the same thing
Yes, if different data is returned then we'll end up flip flopping as each service syncs. This isn't something we considered unfortunately. Perhaps we can make it eventually consistent on upsert. Currently sourcegraph/cmd/repo-updater/repos/types.go Line 735 in 15d54fa
I've tested this and it works as expected.
In the new implementation all instances will use this since, as you guessed, it's use in the streaming inserter. So given that it's used in all code paths now and we've included a bunch of new tests as well as done a fair amount of manual testing I'm fairly confident that it still works. |
I just remember another case we came across. Not sure how it would apply now:
We also have cases we handle like repos swapping names between syncs:
Throw in different external services to make it more fun. EG to make this applicable, imagine if what is synced by ExternalID=1 and ExternalID=2 are different external services? Because the old code treated this globally, it would pick one winner for a certain name. So we would never violate the unique name constraint.
This flip flopping may be fine. For example you could enforce name templates are the same for same codehosts? Then if a codehost flipflops data we could maybe just accept that as the codehost doing something bad? I can't remember if we ever came across something like that, we only specifically cared about name.
I think for merges picking a winner like only min(external_service_id) gets to choose the name may work? But you get into issues like min(external_service_id) having its access token revoked and never syncing (this seems somewhat likely for sourcegraph.com usecase). I think flip flopping is acceptable, as long as we don't flip flop on name.
Great
Sounds good. |
@keegancsmith We have added some logic for resolving name conflicts when they happen, this should prevent the flip-flopping when a repo is renamed for any reason. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, some concerns over the size of the names list I commented on inline. One wishes names were not unique and externalrepospec was the primary key :)
What is the behaviour when syncers run in parallel? I am guessing this conflicting code may run in a strange way. However, I believe that is probably fine to ignore since a future run will converge to the correct values.
return errors.Wrap(err, "syncer.sync.store.list-repos") | ||
} | ||
conflicting = conflicting.Filter(func(r *Repo) bool { | ||
for _, id := range r.ExternalServiceIDs() { | ||
if id == externalServiceID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably not worth it, but it seems this filter could relatively easily be represented in SQL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We wanted to modify ListRepos
to allow filtering by external service id and names, but apparently it is not supported. We decided to keep that change for another PR, even though it feels wrong doing that filtering at runtime
Yes, there's a chance that two syncers could try and modify the same repo in different transactions. One will fail and be requeued and should succeed on the next sync so they'll eventually converge. |
We use the workerutil resetter now instead
This PR makes two main changes:
workerutil
packageSyncer.Run
now enqueues external services into a job queue that are due to be synced every minute.These jobs are picked by up to three sync workers concurrently each worker syncs one service using the new
Syncer.SyncExternalService
method.We rely on database triggers to mark repos as deleted once they no longer exist in any external service. This is based on the state of the
external_service_repos
table which tracks the relationship between repos and external services.Triggers also exist that remove rows from
external_service_repos
when a repo or external service is deleted or soft deleted.@keegancsmith We'd love your feedback specifically on the the code related to
SyncSubset
as we are not 100% sure that this still works as expected.