go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/redisconn/blobcache.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package redisconn
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"github.com/gomodule/redigo/redis"
    22  	"go.opentelemetry.io/otel"
    23  	"go.opentelemetry.io/otel/codes"
    24  
    25  	"go.chromium.org/luci/server/caching"
    26  )
    27  
    28  var tracer = otel.Tracer("go.chromium.org/luci/server/redisconn")
    29  
    30  // redisBlobCache implements caching.BlobCache using Redis.
    31  type redisBlobCache struct {
    32  	Prefix string // prefix to prepend to keys
    33  }
    34  
    35  var _ caching.BlobCache = (*redisBlobCache)(nil)
    36  
    37  func (rc *redisBlobCache) key(k string) string { return rc.Prefix + k }
    38  
    39  // Get returns a cached item or ErrCacheMiss if it's not in the cache.
    40  func (rc *redisBlobCache) Get(ctx context.Context, key string) (blob []byte, err error) {
    41  	ctx, span := tracer.Start(ctx, "go.chromium.org/luci/server.RedisBlobCache.Get")
    42  	defer func() {
    43  		if err != nil {
    44  			span.RecordError(err)
    45  			span.SetStatus(codes.Error, err.Error())
    46  		}
    47  		span.End()
    48  	}()
    49  
    50  	conn, err := Get(ctx)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	defer conn.Close()
    55  
    56  	blob, err = redis.Bytes(conn.Do("GET", rc.key(key)))
    57  	if err == redis.ErrNil {
    58  		return nil, caching.ErrCacheMiss
    59  	}
    60  	return blob, err
    61  }
    62  
    63  // Set unconditionally overwrites an item in the cache.
    64  //
    65  // If 'exp' is zero, the item will have no expiration time.
    66  func (rc *redisBlobCache) Set(ctx context.Context, key string, value []byte, exp time.Duration) (err error) {
    67  	ctx, span := tracer.Start(ctx, "go.chromium.org/luci/server.RedisBlobCache.Set")
    68  	defer func() {
    69  		if err != nil {
    70  			span.RecordError(err)
    71  			span.SetStatus(codes.Error, err.Error())
    72  		}
    73  		span.End()
    74  	}()
    75  
    76  	conn, err := Get(ctx)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	defer conn.Close()
    81  
    82  	if exp == 0 {
    83  		_, err = conn.Do("SET", rc.key(key), value)
    84  	} else {
    85  		_, err = conn.Do("PSETEX", rc.key(key), exp.Nanoseconds()/1e6, value)
    86  	}
    87  	return err
    88  }