-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
add middleware for request prioritization #33951
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
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
a38d71b
add middleware for request prioritization
bohde aec549d
apply review feedback
bohde 8099576
assign repo home page default priority
bohde bde2fd1
use `service.qos` block to configure qos settings
bohde 7e69399
use `qos` block to configure QOS
bohde 5078f67
if the client requests HTML, render an HTML 503 Service Unavailable p…
bohde be160b0
fix indentation
bohde 4fe9e26
relax default values
bohde d2af28d
add error log when dropping a request
bohde 6fb0021
update app.example.ini to match documentation
bohde 312bea9
Merge branch 'main' into rb/request-qos
bohde 6d62c57
Merge branch 'main' into rb/request-qos
GiteaBot 8035a0f
Merge branch 'main' into rb/request-qos
GiteaBot 0d1a9bb
Merge branch 'main' into rb/request-qos
GiteaBot 89ada9d
Merge branch 'main' into rb/request-qos
GiteaBot a601b45
Merge b
8000
ranch 'main' into rb/request-qos
GiteaBot 620225d
Merge branch 'main' into rb/request-qos
GiteaBot 95cd9d1
Merge branch 'main' into rb/request-qos
GiteaBot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next
Next commit
add middleware for request prioritization
This adds a middleware for overload protection, that is intended to help protect against malicious scrapers. It does this by via [`codel`](https://github.com/bohde/codel), which will perform the following: 1. Limit the number of in-flight requests to some user defined max 2. When in-flight requests have reached their max, begin queuing requests, with logged in requests having priority above logged out requests 3. Once a request has been queued for too long, it has a percentage chance to be rejected based upon how overloaded the entire system is. When a server experiences more traffic than it can handle, this has the effect of keeping latency low for logged in users, while rejecting just enough requests from logged out users to keep the service from being overloaded. Below are benchmarks showing a system at 100% capacity and 200% capacity in a few different configurations. The 200% capacity is shown to highlight an extreme case. I used [hey](https://github.com/rakyll/hey) to simulate the bot traffic: ``` hey -z 1m -c 10 "http://localhost:3000/rowan/demo/issues?state=open&type=all&labels=&milestone=0&project=0&assignee=0&poster=0&q=fix" ``` The concurrency of 10 was chosen from experiments where my local server began to experience higher latency. Results | Method | Concurrency | p95 latency | Successful RPS | Requests Dropped | |--------|--------|--------|--------|--------| | QoS Off | 10 | 0.2960s | 44 rps | 0% | | QoS Off | 20 | 0.5667s | 44 rps | 0%| | QoS On | 20 | 0.4409s | 48 rps | 10% | | QoS On 50% Logged In* | 10 | 0.3891s | 33 rps | 7% | | QoS On 50% Logged Out* | 10 | 2.0388s | 13 rps | 6% | Logged in users were given the additional parameter ` -H "Cookie: i_like_gitea=<session>`. Tests with `*` were run at the same time, representing a workload with mixed logged in & logged out users. Results are separated to show prioritization, and how logged in users experience a 100ms latency increase under load, compared to the 1.6 seconds logged out users see.
- Loading branch information
commit a38d71b4cbf795b56fb706470e6e73f7e27a9bf0
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Copyright 2025 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package common | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
user_model "code.gitea.io/gitea/models/user" | ||
"code.gitea.io/gitea/modules/setting" | ||
"code.gitea.io/gitea/modules/web/middleware" | ||
|
||
"github.com/bohde/codel" | ||
) | ||
|
||
type Priority int | ||
|
||
func (p Priority) String() string { | ||
switch p { | ||
case HighPriority: | ||
return "high" | ||
case DefaultPriority: | ||
return "default" | ||
case LowPriority: | ||
return "low" | ||
default: | ||
return fmt.Sprintf("%d", p) | ||
} | ||
} | ||
|
||
const ( | ||
LowPriority = Priority(-10) | ||
DefaultPriority = Priority(0) | ||
HighPriority = Priority(10) | ||
) | ||
|
||
// QoS implements quality of service for requests, based upon whether | ||
1E80 | // or not the user is logged in. All traffic may get dropped, and | |
// anonymous users are deprioritized. | ||
func QoS() func(next http.Handler) http.Handler { | ||
maxOutstanding := setting.Service.QoS.MaxInFlightRequests | ||
if maxOutstanding <= 0 { | ||
maxOutstanding = 10 | ||
} | ||
|
||
c := codel.NewPriority(codel.Options{ | ||
// The maximum number of waiting requests. | ||
MaxPending: setting.Service.QoS.MaxWaitingRequests, | ||
// The maximum number of in-flight requests. | ||
MaxOutstanding: maxOutstanding, | ||
// The target latency that a blocked request should wait | ||
// for. After this, it might be dropped. | ||
TargetLatency: setting.Service.QoS.TargetWaitTime, | ||
}) | ||
|
||
return func(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
ctx := req.Context() | ||
|
||
priority := DefaultPriority | ||
|
||
// If the user is logged in, assign high priority. | ||
data := middleware.GetContextData(req.Context()) | ||
if _, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok { | ||
priority = HighPriority | ||
} else if IsGitContents(req.URL.Path) { | ||
// Otherwise, if the path would is accessing git contents directly, mark as low priority | ||
priority = LowPriority | ||
} | ||
|
||
// Check if the request can begin processing. | ||
err := c.Acquire(ctx, int(priority)) | ||
if err != nil { | ||
// If it failed, the service is over capacity and should error | ||
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable) | ||
return | ||
} | ||
|
||
// Release long-polling immediately, so they don't always | ||
// take up an in-flight request | ||
if strings.Contains(req.URL.Path, "/user/events") { | ||
c.Release() | ||
} else { | ||
defer c.Release() | ||
} | ||
|
||
next.ServeHTTP(w, req) | ||
}) | ||
} | ||
} | ||
|
||
func IsGitContents(path string) bool { | ||
parts := []string{ | ||
"refs", | ||
"archive", | ||
"commit", | ||
"graph", | ||
"blame", | ||
"branches", | ||
"tags", | ||
"labels", | ||
"stars", | ||
"search", | ||
"activity", | ||
"wiki", | ||
"watchers", | ||
"compare", | ||
"raw", | ||
"src", | ||
"commits", | ||
} | ||
|
||
for _, p := range parts { | ||
if strings.Contains(path, p) { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is inval
2F91
id because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.