gotosocial/internal/cache/domain.go
kim d68c04a6c0
[performance] cache recently allowed/denied domains to cut down on db calls (#794)
* fetch creation and fetching domain blocks from db

Signed-off-by: kim <grufwub@gmail.com>

* add separate domainblock cache type, handle removing block from cache on delete

Signed-off-by: kim <grufwub@gmail.com>

* fix sentinel nil values being passed into cache

Signed-off-by: kim <grufwub@gmail.com>

Signed-off-by: kim <grufwub@gmail.com>
2022-09-02 12:17:46 +02:00

107 lines
3.6 KiB
Go

/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cache
import (
"time"
"codeberg.org/gruf/go-cache/v2"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainCache is a cache wrapper to provide URL and URI lookups for gtsmodel.Status
type DomainBlockCache struct {
cache cache.LookupCache[string, string, *gtsmodel.DomainBlock]
}
// NewStatusCache returns a new instantiated statusCache object
func NewDomainBlockCache() *DomainBlockCache {
c := &DomainBlockCache{}
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.DomainBlock]{
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
lm.RegisterLookup("id")
},
AddLookups: func(lm *cache.LookupMap[string, string], block *gtsmodel.DomainBlock) {
// Block can be equal to nil when sentinel
if block != nil && block.ID != "" {
lm.Set("id", block.ID, block.Domain)
}
},
DeleteLookups: func(lm *cache.LookupMap[string, string], block *gtsmodel.DomainBlock) {
// Block can be equal to nil when sentinel
if block != nil && block.ID != "" {
lm.Delete("id", block.ID)
}
},
})
c.cache.SetTTL(time.Minute*5, false)
c.cache.Start(time.Second * 10)
return c
}
// GetByID attempts to fetch a status from the cache by its ID, you will receive a copy for thread-safety
func (c *DomainBlockCache) GetByID(id string) (*gtsmodel.DomainBlock, bool) {
return c.cache.GetBy("id", id)
}
// GetByURL attempts to fetch a status from the cache by its URL, you will receive a copy for thread-safety
func (c *DomainBlockCache) GetByDomain(domain string) (*gtsmodel.DomainBlock, bool) {
return c.cache.Get(domain)
}
// Put places a status in the cache, ensuring that the object place is a copy for thread-safety
func (c *DomainBlockCache) Put(domain string, block *gtsmodel.DomainBlock) {
if domain == "" {
panic("invalid domain")
}
if block == nil {
// This is a sentinel value for (no block)
c.cache.Set(domain, nil)
} else {
// This is a valid domain block
c.cache.Set(domain, copyDomainBlock(block))
}
}
// InvalidateByDomain will invalidate a domain block from the cache by domain name.
func (c *DomainBlockCache) InvalidateByDomain(domain string) {
c.cache.Invalidate(domain)
}
// copyStatus performs a surface-level copy of status, only keeping attached IDs intact, not the objects.
// due to all the data being copied being 99% primitive types or strings (which are immutable and passed by ptr)
// this should be a relatively cheap process
func copyDomainBlock(block *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
return &gtsmodel.DomainBlock{
ID: block.ID,
CreatedAt: block.CreatedAt,
UpdatedAt: block.UpdatedAt,
Domain: block.Domain,
CreatedByAccountID: block.CreatedByAccountID,
CreatedByAccount: nil,
PrivateComment: block.PrivateComment,
PublicComment: block.PublicComment,
Obfuscate: block.Obfuscate,
SubscriptionID: block.SubscriptionID,
}
}