perf(backend): optimize ban lookups and prune upload group map
- Optimize the ban matching middleware by using a read-only transaction (`db.View`) for the initial scan, avoiding the single bbolt write lock on every request when no ban matches. - Implement periodic pruning of stale entries in the upload grouper map to prevent unbounded memory growth over time. - Avoid redundant parsing of the `max_days` form value in the upload handler.
This commit is contained in:
@@ -312,7 +312,11 @@ func (s *BanService) Match(ip string, now time.Time) (MatchedBan, bool, error) {
|
||||
}
|
||||
now = now.UTC()
|
||||
var matched BanRecord
|
||||
err := s.db.Update(func(tx *bbolt.Tx) error {
|
||||
var matchedKey []byte
|
||||
// Read-only scan first: the common case (no match) only takes a concurrent
|
||||
// read transaction, instead of grabbing the single bbolt write lock on every
|
||||
// request that flows through the ban middleware.
|
||||
err := s.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(bansBucket)
|
||||
return bucket.ForEach(func(key, value []byte) error {
|
||||
if matched.ID != "" {
|
||||
@@ -325,20 +329,37 @@ func (s *BanService) Match(ip string, now time.Time) (MatchedBan, bool, error) {
|
||||
if !record.Active(now) || !banTargetMatches(record.Normalized, parsed) {
|
||||
return nil
|
||||
}
|
||||
record.LastMatchedAt = &now
|
||||
record.UpdatedAt = now
|
||||
next, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bucket.Put(key, next); err != nil {
|
||||
return err
|
||||
}
|
||||
matched = record
|
||||
matchedKey = append([]byte(nil), key...) // key bytes are only valid within the txn
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return MatchedBan{Ban: matched, IP: ip}, matched.ID != "", err
|
||||
if err != nil || matched.ID == "" {
|
||||
return MatchedBan{Ban: matched, IP: ip}, matched.ID != "", err
|
||||
}
|
||||
|
||||
// On a hit, record the match time in a short write transaction.
|
||||
matched.LastMatchedAt = &now
|
||||
matched.UpdatedAt = now
|
||||
_ = s.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(bansBucket)
|
||||
data := bucket.Get(matchedKey)
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
var record BanRecord
|
||||
if err := json.Unmarshal(data, &record); err != nil {
|
||||
return nil
|
||||
}
|
||||
record.LastMatchedAt = &now
|
||||
record.UpdatedAt = now
|
||||
next, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return bucket.Put(matchedKey, next)
|
||||
})
|
||||
return MatchedBan{Ban: matched, IP: ip}, true, nil
|
||||
}
|
||||
|
||||
func (r BanRecord) Active(now time.Time) bool {
|
||||
|
||||
Reference in New Issue
Block a user