4a2c66fa06
This PR adds the following items: 1) an in-memory read-only cache thats stores project limit info for projectIDs This cache is stored in-memory since this is expected to be a small amount of data. In this implementation we are only storing in the cache projects that have been accessed. Currently for the largest Satellite (eu-west) there is about 4500 total projects. So storing the storage limit (int64) and the bandwidth limit (int64), this would end up being about 200kb (including the 32 byte project ID) if all 4500 projectIDs were in the cache. So this all fits in memory for the time being. At some point it may not as usage grows, but that seems years out. The cache is a read only cache. When requests come in to upload/download a file, we will read from the cache what the current limits are for that project. If the cache does not contain the projectID, it will get the info from the database (satellitedb project table), then add it to the cache. The only time the values in the cache are modified is when either a) the project ID is not in the cache, or b) the item in the cache has expired (default 10mins), then the data gets refreshed out of the database. This occurs by default every 10 mins. This means that if we update the usage limits in the database, that change might not show up in the cache for 10 mins which mean it will not be reflected to limit end users uploading/downloading files for that time period.. Change-Id: I3fd7056cf963676009834fcbcf9c4a0922ca4a8f
125 lines
4.1 KiB
Go
125 lines
4.1 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package accounting
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/common/memory"
|
|
"storj.io/common/uuid"
|
|
lrucache "storj.io/storj/pkg/cache"
|
|
)
|
|
|
|
var (
|
|
// ErrProjectLimitType error for project limit type.
|
|
ErrProjectLimitType = errs.Class("project limit type error")
|
|
// ErrGetProjectLimit error for getting project limits from database.
|
|
ErrGetProjectLimit = errs.Class("get project limits error")
|
|
// ErrGetProjectLimitCache error for getting project limits from cache.
|
|
ErrGetProjectLimitCache = errs.Class("get project limits from cache error")
|
|
)
|
|
|
|
// ProjectLimitDB stores information about projects limits for storage and bandwidth limits.
|
|
//
|
|
// architecture: Database
|
|
type ProjectLimitDB interface {
|
|
// GetProjectLimits returns current project limit for both storage and bandwidth.
|
|
GetProjectLimits(ctx context.Context, projectID uuid.UUID) (ProjectLimits, error)
|
|
}
|
|
|
|
// ProjectLimitConfig is a configuration struct for project limit.
|
|
type ProjectLimitConfig struct {
|
|
CacheCapacity int `help:"number of projects to cache." releaseDefault:"10000" devDefault:"100"`
|
|
CacheExpiration time.Duration `help:"how long to cache the project limits." releaseDefault:"10m" devDefault:"30s"`
|
|
}
|
|
|
|
// ProjectLimitCache stores the values for both storage usage limit and bandwidth limit for
|
|
// each project ID if they differ from the default limits.
|
|
type ProjectLimitCache struct {
|
|
projectLimitDB ProjectLimitDB
|
|
defaultMaxUsage memory.Size
|
|
defaultMaxBandwidth memory.Size
|
|
|
|
state *lrucache.ExpiringLRU
|
|
}
|
|
|
|
// NewProjectLimitCache creates a new project limit cache to store the project limits for each project ID.
|
|
func NewProjectLimitCache(db ProjectLimitDB, defaultMaxUsage, defaultMaxBandwidth memory.Size, config ProjectLimitConfig) *ProjectLimitCache {
|
|
return &ProjectLimitCache{
|
|
projectLimitDB: db,
|
|
defaultMaxUsage: defaultMaxUsage,
|
|
defaultMaxBandwidth: defaultMaxBandwidth,
|
|
state: lrucache.New(lrucache.Options{
|
|
Capacity: config.CacheCapacity,
|
|
Expiration: config.CacheExpiration,
|
|
}),
|
|
}
|
|
}
|
|
|
|
// GetProjectLimits returns current project limit for both storage and bandwidth.
|
|
func (c *ProjectLimitCache) GetProjectLimits(ctx context.Context, projectID uuid.UUID) (_ ProjectLimits, err error) {
|
|
defer mon.Task()(&ctx, projectID)(&err)
|
|
|
|
projectLimits, err := c.projectLimitDB.GetProjectLimits(ctx, projectID)
|
|
if err != nil {
|
|
return ProjectLimits{}, ErrGetProjectLimit.Wrap(err)
|
|
}
|
|
if projectLimits.Bandwidth == nil {
|
|
defaultBandwidth := c.defaultMaxBandwidth.Int64()
|
|
projectLimits.Bandwidth = &defaultBandwidth
|
|
}
|
|
if projectLimits.Usage == nil {
|
|
defaultUsage := c.defaultMaxUsage.Int64()
|
|
projectLimits.Usage = &defaultUsage
|
|
}
|
|
|
|
return projectLimits, nil
|
|
}
|
|
|
|
// Get returns the storage usage limit for a project ID.
|
|
func (c *ProjectLimitCache) Get(ctx context.Context, projectID uuid.UUID) (ProjectLimits, error) {
|
|
fn := func() (interface{}, error) {
|
|
return c.GetProjectLimits(ctx, projectID)
|
|
}
|
|
projectLimits, err := c.state.Get(projectID.String(), fn)
|
|
if err != nil {
|
|
return ProjectLimits{}, ErrGetProjectLimitCache.Wrap(err)
|
|
}
|
|
limits, ok := projectLimits.(ProjectLimits)
|
|
if !ok {
|
|
return ProjectLimits{}, ErrProjectLimitType.New("cache Get error")
|
|
}
|
|
return limits, nil
|
|
}
|
|
|
|
// GetProjectStorageLimit returns the storage usage limit for a project ID.
|
|
func (c *ProjectLimitCache) GetProjectStorageLimit(ctx context.Context, projectID uuid.UUID) (_ memory.Size, err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
projectLimits, err := c.Get(ctx, projectID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if projectLimits.Usage == nil {
|
|
return c.defaultMaxUsage, nil
|
|
}
|
|
return memory.Size(*projectLimits.Usage), nil
|
|
|
|
}
|
|
|
|
// GetProjectBandwidthLimit return the bandwidth usage limit for a project ID.
|
|
func (c *ProjectLimitCache) GetProjectBandwidthLimit(ctx context.Context, projectID uuid.UUID) (_ memory.Size, err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
projectLimits, err := c.Get(ctx, projectID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if projectLimits.Bandwidth == nil {
|
|
return c.defaultMaxBandwidth, nil
|
|
}
|
|
return memory.Size(*projectLimits.Bandwidth), nil
|
|
}
|