Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(283)

Side by Side Diff: server/config/caching/config.go

Issue 2573403002: server/config: Generic caching backend. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package caching
6
7 import (
8 "fmt"
9 "net/url"
10
11 "github.com/luci/luci-go/common/errors"
12 log "github.com/luci/luci-go/common/logging"
13 "github.com/luci/luci-go/server/config"
14
15 "golang.org/x/net/context"
16 )
17
18 // Schema is the current package's cache schema.
19 const Schema = "v2"
20
21 // Operation is a cache entry operation. Cache entries are all stored in the
22 // same object, with different parameters filled based on the operation that
23 // they represent.
24 type Operation string
25
26 const (
27 // OpGet is the Get operation.
28 OpGet = Operation("Get")
29 // OpGetAll is the GetAll operation.
30 OpGetAll = Operation("GetAll")
31 // OpConfigSetURL is the ConfigSetURL operation.
32 OpConfigSetURL = Operation("ConfigSetURL")
33 )
34
35 // Key is a cache key.
36 type Key struct {
37 // Schema is the schema for this key/value. If schema changes in a backw ards-
38 // incompatible way, this must also change.
39 Schema string `json:"s,omitempty"`
40
41 // Authority is the config authority to use.
42 Authority config.Authority `json:"a,omitempty"`
43
44 // Op is the operation that is being cached.
45 Op Operation `json:"op,omitempty"`
46
47 // Content is true if this request asked for content.
48 Content bool `json:"c,omitempty"`
49
50 // Format is the requesting Key's Format parameter.
51 Format string `json:"f,omitempty"`
52 // FormatData is the requesting Key's FormatData parameter.
53 FormatData string `json:"fd,omitempty"`
54
55 // ConfigSet is the config set parameter. This is valid for "OpGet".
56 ConfigSet string `json:"cs,omitempty"`
57 // Path is the path parameters. This is valid for "OpGet" and "OpGetAll" .
58 Path string `json:"p,omitempty"`
59
60 // GetAllType is the "GetAll" operation type. This is valid for "OpGetAl l".
61 GetAllType config.GetAllType `json:"gat,omitempty"`
62 }
63
64 // ParamHash returns a deterministic hash of all of the key parameters.
65 func (k *Key) ParamHash() []byte {
66 cstr := ""
67 if k.Content {
68 cstr = "y"
69 }
70 return HashParams(k.Schema, string(k.Authority), string(k.Op), cstr, k.F ormat, k.FormatData,
71 k.ConfigSet, k.Path, string(k.GetAllType))
72 }
73
74 // String prints a text representation of the key. No effort is made to ensure
75 // that this representation is consistent or deterministic, and it is not
76 // bound to the cache schema.
77 func (k *Key) String() string { return fmt.Sprintf("CacheKey[%#v]", k) }
78
79 // Params returns the config.Params that are encoded in this Key.
80 func (k *Key) Params() config.Params {
81 return config.Params{
82 Content: k.Content,
83 Authority: k.Authority,
84 Format: k.Format,
85 FormatData: k.FormatData,
86 }
87 }
88
89 // ValueItem is a cache-optimized config.Item projection.
iannucci 2017/01/07 20:53:17 I would explain that this means that it stores the
dnj 2017/01/10 03:29:17 Done.
90 type ValueItem struct {
91 ConfigSet string `json:"cs,omitempty"`
92 Path string `json:"p,omitempty"`
93
94 ContentHash string `json:"ch,omitempty"`
95 Revision string `json:"r,omitempty"`
96
97 Content []byte `json:"c,omitempty"`
98
99 Format string `json:"f,omitempty"`
100 FormatData []byte `json:"fd,omitempty"`
iannucci 2017/01/07 20:53:16 why is formatdata needed again? couldn't it just b
dnj 2017/01/10 03:29:17 b/c we have a single registered Formatter instance
101 }
102
103 // MakeValueItem builds a caching ValueItem from a config.Item.
104 func MakeValueItem(it *config.Item) ValueItem {
105 return ValueItem{
106 ConfigSet: it.ConfigSet,
107 Path: it.Path,
108 ContentHash: it.ContentHash,
109 Revision: it.Revision,
110 Content: []byte(it.Content),
111 Format: it.Format,
112 FormatData: []byte(it.FormatData),
113 }
114 }
115
116 // ConfigItem returns the config.Item equivalent of vi.
117 func (vi *ValueItem) ConfigItem() *config.Item {
118 return &config.Item{
119 Meta: config.Meta{
120 ConfigSet: vi.ConfigSet,
121 Path: vi.Path,
122 ContentHash: vi.ContentHash,
123 Revision: vi.Revision,
124 },
125 Content: string(vi.Content),
126 Format: vi.Format,
127 FormatData: string(vi.FormatData),
128 }
129 }
130
131 // Value is a cache value.
132 type Value struct {
133 // Items is the cached set of config response items.
134 //
135 // For Get, this will either be empty (cached miss) or have a single Ite m
136 // in it (cache hit).
137 Items []ValueItem `json:"i,omitempty"`
138
139 // URL is a URL string.
140 //
141 // Used with GetConfigSetURL.
142 URL string `json:"u,omitempty"`
143 }
144
145 // LoadItems loads a set of config.Item into v's Items field. If items is nil,
146 // v.Items will be nil.
147 func (v *Value) LoadItems(items ...*config.Item) {
148 if len(items) == 0 {
149 v.Items = nil
150 return
151 }
152
153 v.Items = make([]ValueItem, len(items))
154 for i, it := range items {
155 v.Items[i] = MakeValueItem(it)
156 }
157 }
158
159 // SingleItem returns the first config.Item in v's Items slice. If the Items
160 // slice is empty, SingleItem will return nil.
161 func (v *Value) SingleItem() *config.Item {
162 if len(v.Items) == 0 {
163 return nil
164 }
165 return v.Items[0].ConfigItem()
166 }
167
168 // ConfigItems returns the config.Item projection of v's Items slice.
169 func (v *Value) ConfigItems() []*config.Item {
170 if len(v.Items) == 0 {
171 return nil
172 }
173
174 res := make([]*config.Item, len(v.Items))
175 for i := range v.Items {
176 res[i] = v.Items[i].ConfigItem()
177 }
178 return res
179 }
180
181 // DecodeValue loads a Value from is encoded representation.
182 func DecodeValue(d []byte) (*Value, error) {
183 var v Value
184 if err := Decode(d, &v); err != nil {
185 return nil, errors.Annotate(err).Err()
186 }
187 return &v, nil
188 }
189
190 // Encode encodes this Valu.
iannucci 2017/01/07 20:53:16 Value
dnj 2017/01/10 03:29:17 Cool kids say "valu".
191 //
192 // This is offered for convenience, but caches aren't required to use this
193 // encoding.
194 //
195 // The format stores the Value as compressed JSON.
196 func (v *Value) Encode() ([]byte, error) { return Encode(v) }
197
198 // Loader retrieves a Value by consulting the backing backend.
199 //
200 // The input Value is the current cached Value, or nil if there is no current
201 // cached Value. The output Value is the cached Value, if one exists. It is
202 // acceptable to return mutate "v" and/or return it as the output Value.
203 type Loader func(context.Context, Key, *Value) (*Value, error)
204
205 // Backend is a config.Backend implementation that caches responses.
206 //
207 // All cached values are full-content regardless of whether or not full content
208 // was requested.
209 //
210 // Backend caches content and no-content requests as separate cache entries.
211 // This enables one cache to do low-overhead updating against another cache
212 // implementation.
213 type Backend struct {
214 // Backend is the backing Backend.
215 config.Backend
216
217 // HardFailure, if true, means that a failure to retrieve a cached item will
218 // be propagated. If false, a cache error will result in a fall-through to
219 // Backend.
220 HardFailure bool
iannucci 2017/01/07 20:53:16 I would explain that you'd set this to true if the
dnj 2017/01/10 03:29:17 I'll rename to FailOnError. "Fail fast" means some
221
222 // CacheGet retrieves the cached value associated with key. If no such c ached
223 // value exists, CacheGet is responsible for resolving the cache value u sing
224 // the supplied Loaer.
iannucci 2017/01/07 20:53:16 Loader
dnj 2017/01/10 03:29:17 Done.
225 CacheGet func(context.Context, Key, Loader) (*Value, error)
226 }
227
228 // Get implements config.Backend.
229 func (b *Backend) Get(c context.Context, configSet, path string, p config.Params ) (*config.Item, error) {
230 key := Key{
231 Schema: Schema,
232 Authority: p.Authority,
233 Op: OpGet,
234 Content: p.Content,
235 ConfigSet: configSet,
236 Path: path,
237 Format: p.Format,
238 FormatData: p.FormatData,
239 }
240 value, err := b.CacheGet(c, key, b.loader)
241 if err != nil {
242 if b.HardFailure {
243 log.Fields{
244 log.ErrorKey: err,
245 "authority": p.Authority,
246 "configSet": configSet,
247 "path": path,
248 }.Errorf(c, "(Hard Failure) failed to load cache value." )
249 return nil, errors.Annotate(err).Err()
250 }
251
252 log.Fields{
253 log.ErrorKey: err,
254 "authority": p.Authority,
255 "configSet": configSet,
256 "path": path,
257 }.Warningf(c, "Failed to load cache value.")
258 return b.Backend.Get(c, configSet, path, p)
259 }
260
261 it := value.SingleItem()
262 if it == nil {
263 // Sentinel for no config.
264 return nil, config.ErrNoConfig
265 }
266 return it, nil
267 }
268
269 // GetAll implements config.Backend.
270 func (b *Backend) GetAll(c context.Context, t config.GetAllType, path string, p config.Params) ([]*config.Item, error) {
271 key := Key{
272 Schema: Schema,
273 Authority: p.Authority,
274 Op: OpGetAll,
275 Content: p.Content,
276 Path: path,
277 GetAllType: t,
278 Format: p.Format,
279 FormatData: p.FormatData,
280 }
281 value, err := b.CacheGet(c, key, b.loader)
282 if err != nil {
283 if b.HardFailure {
284 log.Fields{
285 log.ErrorKey: err,
286 "authority": p.Authority,
287 "type": t,
288 "path": path,
289 }.Errorf(c, "(Hard Failure) failed to load cache value." )
290 return nil, errors.Annotate(err).Err()
291 }
292
293 log.Fields{
294 log.ErrorKey: err,
295 "authority": p.Authority,
296 "type": t,
297 "path": path,
298 }.Warningf(c, "Failed to load cache value.")
299 return b.Backend.GetAll(c, t, path, p)
300 }
301 return value.ConfigItems(), nil
302 }
303
304 // ConfigSetURL implements config.Backend.
305 func (b *Backend) ConfigSetURL(c context.Context, a config.Authority, configSet string) (u url.URL, err error) {
306 key := Key{
307 Schema: Schema,
308 Authority: a,
309 Op: OpConfigSetURL,
310 ConfigSet: configSet,
311 }
312
313 var value *Value
314 if value, err = b.CacheGet(c, key, b.loader); err != nil {
315 if b.HardFailure {
316 log.Fields{
317 log.ErrorKey: err,
318 "authority": a,
319 "configSet": configSet,
320 }.Errorf(c, "(Hard Failure) failed to load cache value." )
321 err = errors.Annotate(err).Err()
322 return
323 }
324
325 log.Fields{
326 log.ErrorKey: err,
327 "authority": a,
328 "configSet": configSet,
329 }.Warningf(c, "Failed to load cache value.")
330 return b.Backend.ConfigSetURL(c, a, configSet)
331 }
332
333 if value.URL == "" {
334 // Sentinel for no config.
335 err = config.ErrNoConfig
336 return
337 }
338
339 up, err := url.Parse(value.URL)
340 if err != nil {
341 err = errors.Annotate(err).Reason("failed to parse cached URL: % (value)q").D("value", value.URL).Err()
342 return
343 }
344
345 u = *up
346 return
347 }
348
349 // loader runs a cache get against the configured Base backend.
350 //
351 // This should be used by caches that do not have the cached value.
352 func (b *Backend) loader(c context.Context, k Key, v *Value) (*Value, error) {
353 return CacheLoad(c, b.Backend, k, v)
354 }
355
356 // CacheLoad loads k from backend b.
357 //
358 // If an existing cache value is known, it should be supplied as v. Otherwise,
iannucci 2017/01/07 20:53:17 It would be good to explain how the provided v wil
359 // v should be nil.
360 //
361 // This is effectively a Loader function that is detached from a given cache
362 // instance.
363 func CacheLoad(c context.Context, b config.Backend, k Key, v *Value) (rv *Value, err error) {
364 switch k.Op {
365 case OpGet:
366 rv, err = doGet(c, b, k.ConfigSet, k.Path, v, k.Params())
367 case OpGetAll:
368 rv, err = doGetAll(c, b, k.GetAllType, k.Path, v, k.Params())
369 case OpConfigSetURL:
370 rv, err = doConfigSetURL(c, b, k.Authority, k.ConfigSet)
371 default:
372 return nil, errors.Reason("unknown operation: %(op)v").D("op", k .Op).Err()
373 }
374 if err != nil {
375 return nil, err
376 }
377 return
378 }
379
380 func doGet(c context.Context, b config.Backend, configSet, path string, v *Value , p config.Params) (*Value, error) {
381 hadItem := (v != nil && len(v.Items) > 0)
382 if !hadItem {
383 // Initialize empty "v".
384 v = &Value{}
385 }
386
387 // If we have a current item, or if we are requesting "no-content", then
388 // perform a "no-content" lookup.
389 if hadItem || !p.Content {
390 noContentP := p
391 noContentP.Content = false
392
393 item, err := b.Get(c, configSet, path, noContentP)
394 switch err {
395 case nil:
396 if hadItem && (item.ContentHash == v.Items[0].ContentHas h) {
397 // Nothing changed.
398 return v, nil
399 }
400
401 // If our "Get" is, itself, no-content, then this is our actual result.
402 if !p.Content {
403 v.LoadItems(item)
404 return v, nil
405 }
406
407 // If we get here, we are requesting full-content and ou r hash check
408 // showed that the current content has a different hash.
409 break
410
411 case config.ErrNoConfig:
412 v.LoadItems()
413 return v, nil
414
415 default:
416 return nil, errors.Annotate(err).Err()
417 }
418 }
419
420 // Perform a full content request.
421 switch item, err := b.Get(c, configSet, path, p); err {
422 case nil:
423 v.LoadItems(item)
424 return v, nil
425
426 case config.ErrNoConfig:
427 // Empty "config missing" item.
428 v.LoadItems()
429 return v, nil
430
431 default:
432 return nil, errors.Annotate(err).Err()
433 }
434 }
435
436 func doGetAll(c context.Context, b config.Backend, t config.GetAllType, path str ing, v *Value, p config.Params) (*Value, error) {
437 // If we already have a cached value, or if we're requesting no-content, do a
438 // no-content refresh to see if anything has changed.
439 //
440 // Response values are in order, so this is a simple traversal.
441 if v != nil || !p.Content {
442 noContentP := p
443 noContentP.Content = false
444
445 items, err := b.GetAll(c, t, path, noContentP)
446 if err != nil {
447 return nil, errors.Annotate(err).Reason("failed RPC (has h-only)").Err()
448 }
449
450 // If we already have a cached item, validate it.
451 if v != nil && len(items) == len(v.Items) {
452 match := true
453 for i, other := range items {
454 cur := v.Items[i]
455 if cur.ConfigSet == other.ConfigSet && cur.Path == other.Path &&
456 cur.ContentHash == other.ContentHash {
457 continue
458 }
459
460 match = false
461 break
462 }
463
464 // If all configs match, our response hasn't changed.
465 if match {
466 return v, nil
467 }
468 }
469
470 // If we requested no-content, then this is our result.
471 if !p.Content {
472 var retV Value
473 retV.LoadItems(items...)
474 return &retV, nil
475 }
476 }
477
478 // Perform a full-content request.
479 items, err := b.GetAll(c, t, path, p)
480 if err != nil {
481 return nil, errors.Annotate(err).Err()
482 }
483 var retV Value
484 retV.LoadItems(items...)
485 return &retV, nil
486 }
487
488 func doConfigSetURL(c context.Context, b config.Backend, a config.Authority, con figSet string) (*Value, error) {
489 u, err := b.ConfigSetURL(c, a, configSet)
490 switch err {
491 case nil:
492 return &Value{
493 URL: u.String(),
494 }, nil
495
496 case config.ErrNoConfig:
497 return &Value{}, nil
498
499 default:
500 return nil, err
501 }
502 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698