fix: add api level throttler for LIST calls

This commit is contained in:
Harshavardhana 2021-01-28 22:15:38 -08:00
parent dc1a46e5d2
commit 5151c429e4
6 changed files with 124 additions and 14 deletions

View file

@ -36,7 +36,7 @@ const (
// adminAPIHandlers provides HTTP handlers for MinIO admin API. // adminAPIHandlers provides HTTP handlers for MinIO admin API.
type adminAPIHandlers struct { type adminAPIHandlers struct {
mu sync.Mutex mu *sync.Mutex
healSetsMap map[string]healInitSetParams healSetsMap map[string]healInitSetParams
} }
@ -44,6 +44,7 @@ type adminAPIHandlers struct {
func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) { func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) {
adminAPI := adminAPIHandlers{ adminAPI := adminAPIHandlers{
mu: &sync.Mutex{},
healSetsMap: make(map[string]healInitSetParams), healSetsMap: make(map[string]healInitSetParams),
} }

View file

@ -19,9 +19,14 @@ package cmd
import ( import (
"net" "net"
"net/http" "net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/minio/minio/cmd/config/api"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/wildcard" "github.com/minio/minio/pkg/wildcard"
"github.com/rs/cors" "github.com/rs/cors"
) )
@ -48,6 +53,7 @@ func newCachedObjectLayerFn() CacheObjectLayer {
type objectAPIHandlers struct { type objectAPIHandlers struct {
ObjectAPI func() ObjectLayer ObjectAPI func() ObjectLayer
CacheAPI func() CacheObjectLayer CacheAPI func() CacheObjectLayer
Throttler map[string]chan struct{}
} }
// getHost tries its best to return the request host. // getHost tries its best to return the request host.
@ -60,12 +66,39 @@ func getHost(r *http.Request) string {
return r.Host return r.Host
} }
// api throttler constants
const (
listAPI = "LIST"
granularDeadline = 10 * time.Second
)
func parseThrottler(throttle string) map[string]chan struct{} {
th := make(map[string]chan struct{})
for _, v := range strings.Split(throttle, ";") {
vs := strings.SplitN(v, "=", 2)
if len(vs) == 2 {
l, err := strconv.Atoi(vs[1])
if err == nil {
if l >= len(globalEndpoints.Hostnames()) {
l /= len(globalEndpoints.Hostnames())
} else {
l = 1
}
th[vs[0]] = make(chan struct{}, l)
}
}
}
return th
}
// registerAPIRouter - registers S3 compatible APIs. // registerAPIRouter - registers S3 compatible APIs.
func registerAPIRouter(router *mux.Router) { func registerAPIRouter(router *mux.Router) {
// Initialize API. // Initialize API.
api := objectAPIHandlers{ api := objectAPIHandlers{
ObjectAPI: newObjectLayerFn, ObjectAPI: newObjectLayerFn,
CacheAPI: newCachedObjectLayerFn, CacheAPI: newCachedObjectLayerFn,
Throttler: parseThrottler(env.Get(api.EnvAPIRequestsGranularMax, "")),
} }
// API Router // API Router

View file

@ -67,7 +67,7 @@ func waitForLowHTTPReq(maxIO int, maxWait time.Duration) {
// Bucket notification and http trace are not costly, it is okay to ignore them // Bucket notification and http trace are not costly, it is okay to ignore them
// while counting the number of concurrent connections // while counting the number of concurrent connections
maxIOFn := func() int { maxIOFn := func() int {
return maxIO + int(globalHTTPListen.NumSubscribers()) + int(globalHTTPTrace.NumSubscribers()) return maxIO + globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers()
} }
if httpServer := newHTTPServerFn(); httpServer != nil { if httpServer := newHTTPServerFn(); httpServer != nil {

View file

@ -22,6 +22,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@ -82,9 +83,27 @@ func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int
// of the versions of objects in a bucket. // of the versions of objects in a bucket.
func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectVersions") ctx := newContext(r, w, "ListObjectVersions")
defer logger.AuditLog(w, r, "ListObjectVersions", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "ListObjectVersions", mustGetClaimsFromToken(r))
pool := api.Throttler[listAPI]
if pool != nil {
deadlineTimer := time.NewTimer(granularDeadline)
defer deadlineTimer.Stop()
select {
case pool <- struct{}{}:
defer func() { <-pool }()
case <-deadlineTimer.C:
// Send a http timeout message
writeErrorResponse(ctx, w,
errorCodes.ToAPIErr(ErrOperationMaxedOut),
r.URL, guessIsBrowserReq(r))
return
case <-ctx.Done():
return
}
}
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -119,6 +138,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
if forwardStr == "" { if forwardStr == "" {
forwardStr = bucket forwardStr = bucket
} }
if proxyRequestByStringHash(ctx, w, r, forwardStr) { if proxyRequestByStringHash(ctx, w, r, forwardStr) {
return return
} }
@ -152,9 +172,27 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
// MinIO continues to support ListObjectsV1 and V2 for supporting legacy tools. // MinIO continues to support ListObjectsV1 and V2 for supporting legacy tools.
func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectsV2M") ctx := newContext(r, w, "ListObjectsV2M")
defer logger.AuditLog(w, r, "ListObjectsV2M", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "ListObjectsV2M", mustGetClaimsFromToken(r))
pool := api.Throttler[listAPI]
if pool != nil {
deadlineTimer := time.NewTimer(granularDeadline)
defer deadlineTimer.Stop()
select {
case pool <- struct{}{}:
defer func() { <-pool }()
case <-deadlineTimer.C:
// Send a http timeout message
writeErrorResponse(ctx, w,
errorCodes.ToAPIErr(ErrOperationMaxedOut),
r.URL, guessIsBrowserReq(r))
return
case <-ctx.Done():
return
}
}
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -229,9 +267,27 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
// MinIO continues to support ListObjectsV1 for supporting legacy tools. // MinIO continues to support ListObjectsV1 for supporting legacy tools.
func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectsV2") ctx := newContext(r, w, "ListObjectsV2")
defer logger.AuditLog(w, r, "ListObjectsV2", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "ListObjectsV2", mustGetClaimsFromToken(r))
pool := api.Throttler[listAPI]
if pool != nil {
deadlineTimer := time.NewTimer(granularDeadline)
defer deadlineTimer.Stop()
select {
case pool <- struct{}{}:
defer func() { <-pool }()
case <-deadlineTimer.C:
// Send a http timeout message
writeErrorResponse(ctx, w,
errorCodes.ToAPIErr(ErrOperationMaxedOut),
r.URL, guessIsBrowserReq(r))
return
case <-ctx.Done():
return
}
}
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -358,9 +414,27 @@ func proxyRequestByStringHash(ctx context.Context, w http.ResponseWriter, r *htt
// //
func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectsV1") ctx := newContext(r, w, "ListObjectsV1")
defer logger.AuditLog(w, r, "ListObjectsV1", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "ListObjectsV1", mustGetClaimsFromToken(r))
pool := api.Throttler[listAPI]
if pool != nil {
deadlineTimer := time.NewTimer(granularDeadline)
defer deadlineTimer.Stop()
select {
case pool <- struct{}{}:
defer func() { <-pool }()
case <-deadlineTimer.C:
// Send a http timeout message
writeErrorResponse(ctx, w,
errorCodes.ToAPIErr(ErrOperationMaxedOut),
r.URL, guessIsBrowserReq(r))
return
case <-ctx.Done():
return
}
}
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -393,6 +467,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
if forwardStr == "" { if forwardStr == "" {
forwardStr = bucket forwardStr = bucket
} }
if proxyRequestByStringHash(ctx, w, r, forwardStr) { if proxyRequestByStringHash(ctx, w, r, forwardStr) {
return return
} }

View file

@ -36,6 +36,7 @@ const (
apiRemoteTransportDeadline = "remote_transport_deadline" apiRemoteTransportDeadline = "remote_transport_deadline"
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX" EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
EnvAPIRequestsGranularMax = "MINIO_API_REQUESTS_GRANULAR_MAX"
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE" EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE" EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE"
EnvAPICorsAllowOrigin = "MINIO_API_CORS_ALLOW_ORIGIN" EnvAPICorsAllowOrigin = "MINIO_API_CORS_ALLOW_ORIGIN"

View file

@ -85,34 +85,34 @@ func (t *apiConfig) getClusterDeadline() time.Duration {
return t.clusterDeadline return t.clusterDeadline
} }
func (t *apiConfig) getRequestsPool() (chan struct{}, <-chan time.Time) { func (t *apiConfig) getRequestsPool() (chan struct{}, time.Duration) {
t.mu.RLock() t.mu.RLock()
defer t.mu.RUnlock() defer t.mu.RUnlock()
if t.requestsPool == nil { if t.requestsPool == nil {
return nil, nil return nil, time.Duration(0)
}
if t.requestsDeadline <= 0 {
return t.requestsPool, nil
} }
return t.requestsPool, time.NewTimer(t.requestsDeadline).C return t.requestsPool, t.requestsDeadline
} }
// maxClients throttles the S3 API calls // maxClients throttles the S3 API calls
func maxClients(f http.HandlerFunc) http.HandlerFunc { func maxClients(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
pool, deadlineTimer := globalAPIConfig.getRequestsPool() pool, deadline := globalAPIConfig.getRequestsPool()
if pool == nil { if pool == nil {
f.ServeHTTP(w, r) f.ServeHTTP(w, r)
return return
} }
deadlineTimer := time.NewTimer(deadline)
defer deadlineTimer.Stop()
select { select {
case pool <- struct{}{}: case pool <- struct{}{}:
defer func() { <-pool }() defer func() { <-pool }()
f.ServeHTTP(w, r) f.ServeHTTP(w, r)
case <-deadlineTimer: case <-deadlineTimer.C:
// Send a http timeout message // Send a http timeout message
writeErrorResponse(r.Context(), w, writeErrorResponse(r.Context(), w,
errorCodes.ToAPIErr(ErrOperationMaxedOut), errorCodes.ToAPIErr(ErrOperationMaxedOut),