Documentation
¶
Index ¶
- func Provide(cfg Config, lc fx.Lifecycle, urldb *URLDB, tele telemetry.Telemetery, ...) urlrepo.Repository
- type BufferedVisits
- func (r *BufferedVisits) Create(ctx context.Context, u url.URL) error
- func (r *BufferedVisits) Flush(ctx context.Context) error
- func (r *BufferedVisits) FromShortURL(ctx context.Context, key string) (url.URL, error)
- func (r *BufferedVisits) IncrementVisits(_ context.Context, key string) error
- func (r *BufferedVisits) IncrementVisitsBatch(ctx context.Context, deltas map[string]uint64) error
- func (r *BufferedVisits) Pending() int
- func (r *BufferedVisits) Update(ctx context.Context, u url.URL) error
- type CacheConfig
- type Cached
- func (r *Cached) Create(ctx context.Context, u url.URL) error
- func (r *Cached) FromShortURL(ctx context.Context, key string) (url.URL, error)
- func (r *Cached) IncrementVisits(ctx context.Context, key string) error
- func (r *Cached) IncrementVisitsBatch(ctx context.Context, deltas map[string]uint64) error
- func (r *Cached) Len() int
- func (r *Cached) Update(ctx context.Context, u url.URL) error
- type Config
- type URLDB
- func (r *URLDB) Create(ctx context.Context, u url.URL) error
- func (r *URLDB) FromShortURL(ctx context.Context, key string) (url.URL, error)
- func (r *URLDB) IncrementVisits(ctx context.Context, key string) error
- func (r *URLDB) IncrementVisitsBatch(ctx context.Context, deltas map[string]uint64) error
- func (r *URLDB) Update(ctx context.Context, u url.URL) error
- type VisitsConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Provide ¶
func Provide( cfg Config, lc fx.Lifecycle, urldb *URLDB, tele telemetry.Telemetery, logger *zap.Logger, ) urlrepo.Repository
Provide assembles the repository the service talks to.
The database sits at the bottom; visit buffering wraps it so that counting a redirect no longer writes; the cache wraps that so that a redirect usually does not read either. Both layers are decorators over the same interface, so switching either off in the configuration leaves a shorter stack and changes nothing else.
Types ¶
type BufferedVisits ¶
type BufferedVisits struct {
// contains filtered or unexported fields
}
BufferedVisits accumulates visit counts in memory and writes them out in batches.
Recording a visit used to be an UPDATE against a single row on the redirect path, so the busiest links contended on their own row and every read cost a write. Here a visit only increments a counter under a mutex; a background goroutine folds the counters into one statement every FlushInterval, or sooner when too many keys have piled up.
The trade is deliberate: a crash loses at most one interval of counts, and a counter read straight from the database lags by up to one interval. Neither matters for a visit tally, and both buy the redirect path its write back.
func NewBufferedVisits ¶
func NewBufferedVisits( cfg VisitsConfig, lc fx.Lifecycle, next urlrepo.Repository, tele telemetry.Telemetery, logger *zap.Logger, ) *BufferedVisits
func (*BufferedVisits) Flush ¶
func (r *BufferedVisits) Flush(ctx context.Context) error
Flush writes everything buffered so far. Counts are taken out of the buffer before the write and put back if it fails, so a database blip delays them rather than dropping them.
func (*BufferedVisits) FromShortURL ¶
func (*BufferedVisits) IncrementVisits ¶
func (r *BufferedVisits) IncrementVisits(_ context.Context, key string) error
IncrementVisits records a visit in memory. It does not touch the database and so does not fail, which is why the redirect path can afford to call it on every request.
func (*BufferedVisits) IncrementVisitsBatch ¶
IncrementVisitsBatch passes through: it is the drain the buffer itself writes into.
func (*BufferedVisits) Pending ¶
func (r *BufferedVisits) Pending() int
Pending reports how many keys are waiting to be written, for tests and for diagnostics.
type CacheConfig ¶
type CacheConfig struct {
// Enabled turns the cache off entirely, which is useful for seeing what it buys.
Enabled bool `json:"enabled" koanf:"enabled"`
// Size is how many entries to keep before evicting the least recently used one.
Size int `json:"size" koanf:"size"`
// TTL bounds how long an entry is trusted. Entries are also capped by the expiry of the
// url they hold, so a url can never outlive its expiry by sitting in the cache.
TTL time.Duration `json:"ttl" koanf:"ttl"`
// NegativeTTL is how long a miss is remembered. Keys are short enough to guess at, so
// without this a scan for keys that do not exist reaches the database on every request.
// Keep it small: until it lapses, an instance stays blind to a key another instance just
// created.
NegativeTTL time.Duration `json:"negative_ttl" koanf:"negative_ttl"`
}
CacheConfig tunes the read path.
A shortener serves far more redirects than it accepts creations, and what a redirect needs — the target of a key — never changes once written. That makes it about the most cacheable thing there is.
type Cached ¶
type Cached struct {
// contains filtered or unexported fields
}
Cached answers lookups from memory where it can.
It is a decorator: it satisfies the same repository interface as the layer beneath it and can be left out of the stack entirely without anything else noticing. Only reads are served from memory; writes always go through, and invalidate the key they touch.
The cache is per instance. Two consequences worth naming: a negative entry keeps one instance from seeing a key another instance just created until it lapses, and the visit counts it hands back are whatever they were when the entry was filled. Nothing serves those counts to a caller, but they should not be mistaken for current.
func NewCached ¶
func NewCached( cfg CacheConfig, next urlrepo.Repository, tele telemetry.Telemetery, logger *zap.Logger, ) *Cached
func (*Cached) FromShortURL ¶
func (*Cached) IncrementVisits ¶
IncrementVisits passes straight through. Visit counts are not what the cache is protecting, and the layer beneath is where they are folded together.
func (*Cached) IncrementVisitsBatch ¶
type Config ¶
type Config struct {
Cache CacheConfig `json:"cache" koanf:"cache"`
Visits VisitsConfig `json:"visits" koanf:"visits"`
}
Config tunes the layers that sit between the service and the database.
type URLDB ¶
type URLDB struct {
// contains filtered or unexported fields
}
func ProvideURLDB ¶
func (*URLDB) FromShortURL ¶
func (*URLDB) IncrementVisits ¶
func (*URLDB) IncrementVisitsBatch ¶
type VisitsConfig ¶
type VisitsConfig struct {
// Enabled turns buffering off, restoring one write per redirect.
Enabled bool `json:"enabled" koanf:"enabled"`
// FlushInterval is how long a counter may sit in memory before being written out. It also
// bounds what a crash can lose.
FlushInterval time.Duration `json:"flush_interval" koanf:"flush_interval"`
// MaxBuffered is how many distinct keys may accumulate before a flush is triggered early,
// so that a burst does not grow the buffer without bound.
MaxBuffered int `json:"max_buffered" koanf:"max_buffered"`
}
VisitsConfig tunes visit counting.
Counting a visit synchronously turns every redirect into a write against a single row, which makes that row a lock hotspot for exactly the links that are most popular. Buffering trades an exact, instantly visible counter — which nobody needs — for one write per batch.