@@ -23,6 +23,7 @@ import (
2323type Tiered struct {
2424 caches []Cache
2525 metadata * metadatadb.Store
26+ etags * metadatadb.Map [Key , string ]
2627 namespace Namespace
2728}
2829
@@ -40,7 +41,7 @@ func MaybeNewTiered(ctx context.Context, caches []Cache, metadata *metadatadb.St
4041 if metadata == nil {
4142 panic ("Tiered cache requires a metadata store" )
4243 }
43- return Tiered {caches : caches , metadata : metadata }
44+ return Tiered {caches : caches , metadata : metadata , etags : tieredETags ( metadata , "" ) }
4445}
4546
4647type authoritativeCache struct {
@@ -70,21 +71,27 @@ func (t Tiered) Close() error {
7071
7172// Create a new object. All underlying caches will be written to in sequence.
7273func (t Tiered ) Create (ctx context.Context , key Key , headers http.Header , ttl time.Duration , opts ... Option ) (Writer , error ) {
73- rawETag , _ , err := createETag (opts ... )
74+ rawETag , quotedETag , err := createETag (opts ... )
7475 if err != nil {
7576 return nil , errors .WithStack (err )
7677 }
7778 createOpts := []Option {WithETag (rawETag )}
7879
80+ replaceETag , err := t .replacementETag (ctx , key , quotedETag )
81+ if err != nil {
82+ return nil , err
83+ }
84+
7985 // The first error will cancel all outstanding writes.
8086 ctx , cancel := context .WithCancelCause (ctx )
8187
8288 tw := & tieredWriter {
83- writers : make ([]Writer , len (t .caches )),
84- cancel : cancel ,
85- etags : t .etags (),
86- key : key ,
87- rawETag : rawETag ,
89+ writers : make ([]Writer , len (t .caches )),
90+ cancel : cancel ,
91+ etags : t .etags ,
92+ key : key ,
93+ etag : quotedETag ,
94+ replaceETag : replaceETag ,
8895 }
8996 // Note: we can't use errgroup here because we do not want to cancel the context on Wait().
9097 wg := sync.WaitGroup {}
@@ -108,6 +115,18 @@ func (t Tiered) Create(ctx context.Context, key Key, headers http.Header, ttl ti
108115 }
109116}
110117
118+ func (t Tiered ) replacementETag (ctx context.Context , key Key , newETag string ) (bool , error ) {
119+ headers , err := t .caches [len (t .caches )- 1 ].Stat (ctx , key )
120+ switch {
121+ case errors .Is (err , os .ErrNotExist ):
122+ return false , nil
123+ case err != nil :
124+ return false , errors .WithStack (err )
125+ default :
126+ return headers .Get (ETagKey ) != newETag , nil
127+ }
128+ }
129+
111130// Delete from all underlying caches. All errors are returned.
112131func (t Tiered ) Delete (ctx context.Context , key Key ) error {
113132 wg := sync.WaitGroup {}
@@ -118,7 +137,7 @@ func (t Tiered) Delete(ctx context.Context, key Key) error {
118137 wg .Wait ()
119138 err := errors .Join (errs ... )
120139 if err == nil {
121- t .deleteETag (key )
140+ t .etags . Delete (key )
122141 }
123142 return err
124143}
@@ -304,16 +323,12 @@ func (t Tiered) backfillReader(ctx context.Context, key Key, src io.ReadCloser,
304323
305324const tieredETagsMap = "cache-etags"
306325
307- func (t Tiered ) etags () * metadatadb.Map [Key , string ] {
308- return metadatadb .NewMap [Key , string ](t .metadata .Namespace (string (t .namespace )), tieredETagsMap )
309- }
310-
311- func (t Tiered ) deleteETag (key Key ) {
312- t .etags ().Delete (key )
326+ func tieredETags (metadata * metadatadb.Store , namespace Namespace ) * metadatadb.Map [Key , string ] {
327+ return metadatadb .NewMap [Key , string ](metadata .Namespace (string (namespace )), tieredETagsMap )
313328}
314329
315330func (t Tiered ) invalidateStale (ctx context.Context , c Cache , key Key , headers http.Header ) bool {
316- want , ok := t .etags () .Get (key )
331+ want , ok := t .etags .Get (key )
317332 if ! ok || want == headers .Get (ETagKey ) {
318333 return false
319334 }
@@ -470,13 +485,14 @@ func (t Tiered) Stats(ctx context.Context) (Stats, error) {
470485}
471486
472487type tieredWriter struct {
473- writers []Writer
474- cancel context.CancelCauseFunc
475- etags * metadatadb.Map [Key , string ]
476- key Key
477- rawETag string
478- closed bool
479- aborted bool
488+ writers []Writer
489+ cancel context.CancelCauseFunc
490+ etags * metadatadb.Map [Key , string ]
491+ key Key
492+ etag string
493+ replaceETag bool
494+ closed bool
495+ aborted bool
480496}
481497
482498var _ Writer = (* tieredWriter )(nil )
@@ -501,12 +517,8 @@ func (t *tieredWriter) Close() error {
501517 }
502518 wg .Wait ()
503519 err := errors .Join (errs ... )
504- if err == nil && ! t .aborted && t .etags != nil {
505- quoted , qerr := FormatETag (t .rawETag )
506- if qerr != nil {
507- return errors .WithStack (qerr )
508- }
509- t .etags .Set (t .key , quoted )
520+ if err == nil && ! t .aborted && t .replaceETag {
521+ t .etags .Set (t .key , t .etag )
510522 }
511523 return err
512524}
@@ -531,7 +543,7 @@ func (t Tiered) Namespace(namespace Namespace) Cache {
531543 for i , c := range t .caches {
532544 namespaced [i ] = c .Namespace (namespace )
533545 }
534- return Tiered {caches : namespaced , metadata : t .metadata , namespace : namespace }
546+ return Tiered {caches : namespaced , metadata : t .metadata , etags : tieredETags ( t . metadata , namespace ), namespace : namespace }
535547}
536548
537549// ListNamespaces returns unique namespaces from all underlying caches.
0 commit comments