Skip to content

Deal with github bridge import rate limit - #585

Merged
MichaelMure merged 16 commits into
masterfrom
dev-gh-bridge
Apr 10, 2021
Merged

MichaelMure merged 16 commits into
masterfrom
dev-gh-bridge

Conversation

@rng-dynamics

@rng-dynamics rng-dynamics commented Feb 28, 2021 •

Copy link
Copy Markdown
Contributor

An attempt to solve #315.
Changes:

  • New GraphQL queries: It is much more efficient with respect to the rate limit of the Github API.
  • If the Github rate limit is hit, then git-bug sleeps until the limit is reset.
  • Replaces the iterator based import interface with a Go-channel based interface
  • Fetching the data in a separate Go-routine: The import is faster.

@rng-dynamics
rng-dynamics marked this pull request as draft February 28, 2021 20:34
@MichaelMure

Copy link
Copy Markdown
Contributor

Let me tell you, I like this very much.

Ping me when you think it's ready.

@rng-dynamics

Copy link
Copy Markdown
Contributor Author

Yes and thanks

@bqv

bqv commented Mar 2, 2021

Copy link
Copy Markdown

Testing on nixpkgs! It's trudging along, but shows no signs of stopping due to ratelimit, or errors, so all good so far :)

I think I will attempt to bridge pull all of nixpkgs's issues

@mohe2015

This comment has been minimized.

@bqv

bqv commented Mar 2, 2021 •

Copy link
Copy Markdown

So, mine got much further, but also failed, at this point:

40690 new comment: 6a30f67f89414c2f965b858853e8f371f27e0d2e0107e96b26360c71a904ec55
 40691 new identity: 06bd285f9a0e3fd8d275ec3ae36b6fdf55a83ef8
 40692 new issue: c4af3427c13b39210c78c4c80ac81d494c8d9a06
 40693 new comment: 3ee4cf788bae8a08271a9b4fa6f8e901ac9054a78dad12742dc7ca2be9b9559e
 40694 new comment: 1b8b365780dd08865429bf9a7170329508d1ed28905be92fa73344b71433ce21
 40695 changed label: 7fc28960203a687e2e5dfadce3e0ffde247cf5041a0acfc6d43857bcdd2bab3d
 40696 new comment: c8419e39641f36484b6715b459446f334b2f9ea6c561d06aa6d89d735eee000a
 40697 new comment: 9455a1c65f200986a504c21299adfe79dc5ff54fba468341a9cd25be65f5d8fa
 40698 new comment: 5f4dce026e88208d7e56688518a056dada29c47873de94d1f16c36ec920ffcc4
 40699 changed label: c69107ad7555f0f156dc77fc8ea07c558b988a11d2004ad0dc3cc813b2fe2f17
 40700 &{{MDU6SXNzdWUzMjQ3Mjg4Nzc= 2018-05-20 18:07:08 +0000 UTC <nil>}    40827  https://github.com/NixOS/nixpkgs/is\
       sues/40827}
 40701 import error: issue creation: title is empty
 40702 imported 9159 issues and 3014 identities with default bridge
 40703 /srv/git/github.com/nixos/nixpkgs $

No clue why yours happened though

@mohe2015

mohe2015 commented Mar 2, 2021

Copy link
Copy Markdown

https://docs.github.com/en/graphql/overview/resource-limitations says there is a per hour ratelimit so maybe I should just wait one hour then. I still wonder why ctrl-c didn't work though

@bqv

bqv commented Mar 2, 2021

Copy link
Copy Markdown

@rng-dynamics note I did try this with an earlier commit. If you've fixed the issue already, I'm happy to rebuild and retry

@bqv

bqv commented Mar 2, 2021

Copy link
Copy Markdown

@mohe2015 as I retry with the same code currently, it does alert me loudly that it is "SLEEPING FOR 1855 SECONDS"

@mohe2015

mohe2015 commented Mar 2, 2021 •

Copy link
Copy Markdown

Yeah, just read the code (don't know go though) and found out it's supposed to print something. But for me it just hangs.

@mohe2015

mohe2015 commented Mar 2, 2021

Copy link
Copy Markdown

I did a clean

git clone [email protected]:NixOS/nixpkgs.git
cd nixpkgs
git bug bridge connect
git bug bridge pull

btw

@mohe2015

This comment has been minimized.

@mohe2015

mohe2015 commented Mar 2, 2021

Copy link
Copy Markdown

Can I somehow clean all git-bug's data for debugging?

@bqv

bqv commented Mar 2, 2021

Copy link
Copy Markdown

Presumably deleting all refs/bugs, then a git gc (aggressive)

@mohe2015

mohe2015 commented Mar 3, 2021

Copy link
Copy Markdown

For me this happens reproducibly so maybe you could also try with the latest commit:

git bug version --commit
689b640bbbb801772d9c5c4bd428d4ec750f00ce

@bqv

bqv commented Mar 3, 2021

Copy link
Copy Markdown

New error:

 40941 SLEEPING 1855 seconds
 40942 import error: context deadline exceeded
 40943 imported 0 issues and 0 identities with default bridge

@MichaelMure

Copy link
Copy Markdown
Contributor

Presumably deleting all refs/bugs, then a git gc (aggressive)

GC is not necessary. There is some target in the makefile to automate cleaning: make clean-local-bugs and make clean-local-identities

@rng-dynamics

rng-dynamics commented Mar 3, 2021 •

Copy link
Copy Markdown
Contributor Author

I would like to kindly ask you for a few more days. There are two bugs in the current code: one is a deadlock in certain situations (which surfaced by refactoring) and one has to do with empty titles of some github issues.
@mohe2015: you are experiencing the deadlock.
I will push a fix only when I have fixed both bugs and when I could test nixpks myself.

Kindly note that this is a Draft PR and is still work in progress.

Comment thread bridge/github/import_mediator.go Outdated
break
}
issueCursor = issues.PageInfo.EndCursor
for _, issueNode := range issues.Nodes {

@MichaelMure MichaelMure Mar 3, 2021 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I might be wrong but when you have a loop pushing to a channel like that, you might need to wrap it a select that also monitor the context, so that the loop can exit properly when needed.

Something like that:

	for _, foo := range bar {
		// do something with foo
		foo2 := foo + "hello"
		
		select {
		case <-ctx.Done():
			return
		case out<-foo2:
		}
	}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This might explain why the import doesn't stop properly like in #585 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, you are right, I included it in one of my commits.

@MichaelMure

Copy link
Copy Markdown
Contributor

Suggestion to find the races (and possibly deadlock):

  • setup the import in a go test (with hardcoded value if needed)
  • run this test with go test -race to enable the race detector. This should pinpoint if and where you have flaw in your concurrency handling.

Deadlock are trickier but the above might give you some hints. Otherwise, maybe https://github.com/sasha-s/go-deadlock ?

@mohe2015

mohe2015 commented Mar 3, 2021

Copy link
Copy Markdown

@rng-dynamics Sure, no problem. It's our fault if we try too eagerly. But thanks so we now know that it's expected to not work yet.

@bqv

bqv commented Mar 3, 2021

Copy link
Copy Markdown

Kindly note that this is a Draft PR and is still work in progress.

Of course. No expectations, just wanted to help test :)

The old implementation of the github bridge used maps to store several
channels holding data obtained from the Github API. Removing the maps and
simply packing data and channels together in a struct and passing it
through one single channel makes the program simpler in terms of
concurrency and, additionally, enables the garbage collector to free the
memory gradually without any additional provisions.
@rng-dynamics

Copy link
Copy Markdown
Contributor Author

Importing the issues from https://github.com/MichaelMure/git-bug I get:

imported 227 issues and 153 identities with default bridge

real	2m12.048s
user	0m15.798s
sys	0m12.006s

Importing from https://github.com/datalad/datalad:

imported 2953 issues and 139 identities with default bridge

real	29m21.913s
user	7m33.391s
sys	4m50.771s

Importing from https://github.com/NixOS/nixpkgs:

imported 19986 issues and 5223 identities with default bridge

real	457m3.308s
user	316m12.660s
sys	91m54.289s

@rng-dynamics

Copy link
Copy Markdown
Contributor Author

I still have to change the way the rate limit is communicated to the user.

@mohe2015

mohe2015 commented Mar 15, 2021 •

Copy link
Copy Markdown

@rng-dynamics iotop, kde system monitor and iostat report a disk write of ~150M/s by git-bug, is that realistic? I'm testing with https://github.com/NixOS/nixpkgs. Storage use seems to grow with ~0.5M/s (measured by eyes on `df --block-size=1M)

@rng-dynamics

rng-dynamics commented Mar 15, 2021 •

Copy link
Copy Markdown
Contributor Author

@mohe2015: Good question; On my system I measure ~50MB/s with iostat. The difference in disk usage in the .git folder is 1.835GB (comparing before and after importing all the issues of https://github.com/NixOS/nixpkgs). The import takes 457 minutes. That is, the actual occupied disk space grows only with an average of ~67KB per second.

I do not fully know how the data model of git-bug works. I can only speculate about the cause for these disk writes: Having a short peek at the document describing the data model makes me wonder if we are overwriting a lot of files. The bridge adds one item after another to git-bug. But operations in the git-bug data model are gathered into OperationPacks (which are saved in git blobs). The data model also mentions git rebase, which could also be a cause for changing files. Well, ... I might be wrong. 🤔

@rng-dynamics
rng-dynamics marked this pull request as ready for review March 27, 2021 21:14
@rng-dynamics
rng-dynamics marked this pull request as draft March 28, 2021 21:17
@MichaelMure

Copy link
Copy Markdown
Contributor

@mohe2015 so you have a 1.1GB index for the full text search ... this is not good :-| ... but nothing to do with this PR.

@MichaelMure

Copy link
Copy Markdown
Contributor

@rng-dynamics if you think this branch is better than what is in master, I'd be happy to merge it already even if not perfect.

Also, if you rebase your branch, the two issues you faced should be fixed now.

@rng-dynamics

Copy link
Copy Markdown
Contributor Author

@rng-dynamics if you think this branch is better than what is in master, I'd be happy to merge it already even if not perfect.

@MichaelMure: You are right, we should try to merge it. It's for sure much better than what is in master now. In my manual tests the import from most github repositories works. The remaining troubles which I am aware of are exposed in #606 and #609.
The PR is ready from my side. Please have a look at the code and let me know if you would like to see any changes or additional information before we can merge it. I am happy to receive suggestions.

@MichaelMure MichaelMure left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is great, but the rate-limiting feedback should be reworked to let the UIs choose how to present the information to the users.

Comment thread bridge/github/import.go Outdated
func (gi *githubImporter) Init(_ context.Context, repo *cache.RepoCache, conf core.Configuration) error {
func (gi *githubImporter) Init(_ context.Context, _ *cache.RepoCache, conf core.Configuration) error {
gi.conf = conf
return nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So you moved the credential loading + client creation in ImportAll. Not what I imagined but I guess that make sense.
My tiny annoyance with that though is that this has now a different behavior than the other bridges. Could you make the same change there as well?

@rng-dynamics rng-dynamics Apr 5, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My original intention was to simplify the code. Looking at it now makes me also wonder if it really makes any significant difference. The difference in the local code seems to be: When doing the credential loading + client creation in ImportAll, then we do not have to save a *githubv4.Client variable in the githubImporter struct (importMediator already holds the *githubv4.Client), and there is no risk in having two non-identical *cache.RepoCache parameters in the calls to Init and ImportAll.

I could undo this change and move the credential loading + client creation back into Init if that's what you prefer. Please let me know again what you prefer: (1) move credential loading + client creation back into Init, or (2) keeping it in ImportAll and make the same change in all the other bridges.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As you said there is not much difference so both is fine to me as long as it's consistent. (2) has the nice property of avoiding the extra value in the struct, so let's do that one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did that with 3d14e2e for the Gitlab and Jira bridges. There was no need to change anything in the Launchpad bridge.

Comment thread bridge/github/import.go Outdated
var nextEvent ImportEvent
var err error
for {
// We need the current event and one look ahead event.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpicking: it'd be great if you could explain why this structure with look ahead is necesary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Before I add the comment to the code I would like to ask you for additional input: The reason for using a look ahead element is that in case a github issue is followed by a github issue edit, then in the call to NewBugRaw we use both, the github issue and the first github issue edit, to create the bug instance. This behaviour originates from the previous versions of the github bridge and I adopted it. It seems that this is due to the way github serves its issues on the API. (The same is true for comments.) My question is the following: Is this behaviour necessary? Would it be okay to simply create a (possibly empty) bug instance and then apply the first github issue edit in order to create the issue/bug content. This would cause the edit-history of a github issue and a corresponding git-bug bug instance to not be equal (one might display without "edit" and the other might display with "edit"). I assume that would not be acceptable - right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't that because on github it's sort of as if the initial creation of an issue is (creation+comment), while in git-bug it's the same operation? I don't remember exactly.

I assume that would not be acceptable - right?

At least not desirable. But your code does that properly at the moment, no?

Just a thought, but there might be a way to simplify. Instead of maintaining two two values currEvent and nextEvent:

  • simply iterate on the event stream
  • when you know you need an extra event, consume an extra one

@rng-dynamics rng-dynamics Apr 9, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Isn't that because on github it's sort of as if the initial creation of an issue is (creation+comment), while in git-bug it's the same operation? I don't remember exactly.

In github the issue holds the most recent content. Because we want to imitate the history, we sometimes create the bug instance from the plain github issue (if there are no edits in github) and sometimes we create the bug from the github issue together with its first edit (if there are edits in github).
But that's also the reason why we do not know whether or not we need the next event before we looked at it.

I assume that would not be acceptable - right?

At least not desirable. But your code does that properly at the moment, no?

Yes, it is handled properly.

If you want to get rid of the look ahead, then I think it would be best to make importMediator provide more context and add a field (say hasEdits bool) to IssueEvent. Then we just know whether or not there are edits and we do not have to look ahead. (And we would have to add a similar data field to TimelineEvent, because it holds comments which might be followed by comment edits. It's the same situation.)

For now I only added a comment to the code with ede5e21 explaining why look ahead is necessary. Let me know what you think.

Comment thread bridge/github/import.go Outdated
event := gi.mediator.NextImportEvent()
// consume (and use) all message events
if e, ok := event.(MessageEvent); ok {
fmt.Println(e.msg)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The bridge itself should not write anything, this should be left to do by the various UI (CLI, termui, webui). Instead, there is this out channel where you write some core.ImportResult (might not be the best naming).
It looks like you have a need for an arbitrary-ish user feedback for the rate limiting? In that case we should define either 1) an event for an arbitrary message ImportEventMessage or 2) an event for the rate limiting ImportEventRateLimiting. The second one makes probably more sense as it would allow the UIs to special case the handling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did that with 0fd5701

for i := 0; i < retries; i++ {
// wait a few seconds before retry
sleepTime := time.Duration(8*(i+1)) * time.Second
timer := time.NewTimer(sleepTime)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no big deal, but FYI there is a simpler way: https://stackoverflow.com/a/62287775/6594169

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I looked at the docs which made me reflect on the following: With time.NewTimer it is possible to cancel/stop the timer in case it is not needed anymore (e.g., in case the context.Done() channel is closed). That's not possible with the time.After call. The effective difference is that the memory of the timer will be freed only after the timer is really expired. Here that is not a big difference because the goroutine is sleeping only a few seconds. But a bit further down when we are handling the rate limiting, then the goroutine may be sleeping for up to an hour and canceling/stoping the timer might be favorable:
https://github.com/MichaelMure/git-bug/blob/b2e98ef07f3083db303099fbcedbd98a33ad1164/bridge/github/import_mediator.go#L413

@@ -0,0 +1,436 @@
package github

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is GREAT. Nice work to have come up with this structure.

rng-dynamics and others added 5 commits April 8, 2021 22:48
Gitlab and Jira bridge: move credential loading and client creation from
`Init` to `ImportAll` in order to harmonize the behaviour of the
different bridges.
The Github bridge itself should not write anything. This commit removes
code writing to stdout and itroduces an event `ImportEventRateLimiting`
to `core.ImportResult` in order to inform about a rate limiting situation
of the Github GraphQL API. Now the communication with the user is
delegated to the various user interfaces.
@MichaelMure

Copy link
Copy Markdown
Contributor

I fixed the merge conflict and did some minor cleanup. Most of those are insignificant but there was an unhandled error : 10a80f1#diff-3ae542ca4dfb02e8b746658def040e984685424467c3e158d7494768e55fa90dR571-R573

If all if fine for you, we can merge 🎉 🎉 !

@MichaelMure

Copy link
Copy Markdown
Contributor

Actually no, the gitlab bridge seems to be broken now

@rng-dynamics

Copy link
Copy Markdown
Contributor Author

I will have to do some manual testing to see what is the cause. (Not all tests are running on my machine due to missing API tokens. Also the CI did not run the tests before you approved them.)

@rng-dynamics

Copy link
Copy Markdown
Contributor Author

It's not only the gitlab bridge but also the github bridge.
I believe the error originates from our change of moving the credential loading and client creation from Init to ImportAll. The tests actually use a different *RepoCache for Init (to load the credentials) and for ImportAll. I have not been aware of that.

https://github.com/MichaelMure/git-bug/blob/bc5f618eba812859bf87ce2c31b278bd518d4555/bridge/github/export_test.go#L219-L229

https://github.com/MichaelMure/git-bug/blob/bc5f618eba812859bf87ce2c31b278bd518d4555/bridge/gitlab/export_test.go#L225-L235

I don't really understand why I didn't see that error for the github bridge in the CI in b2e98ef.

@MichaelMure

Copy link
Copy Markdown
Contributor

Yeah I don't get it either why this failure happened. It would require some debugging but well it's working now so let's move on and merge.

Great work ! :-)

@MichaelMure
MichaelMure merged commit d09b9d7 into master Apr 10, 2021
@MichaelMure
MichaelMure deleted the dev-gh-bridge branch April 10, 2021 07:50
@rng-dynamics

Copy link
Copy Markdown
Contributor Author

Thanks for merging it!

@bqv

bqv commented Apr 13, 2021

Copy link
Copy Markdown

You are both wonderful people, thanks for the work!

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.

4 participants