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

Side by Side Diff: appengine/config_service/projects.py

Issue 2931673003: config_service: fetch repos and metadata concurrently (Closed)
Patch Set: use ctx.memcache_[gs]et Created 3 years, 6 months 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
« no previous file with comments | « appengine/config_service/api_test.py ('k') | appengine/config_service/projects_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2015 The LUCI Authors. All rights reserved. 1 # Copyright 2015 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 """Provides info about projects (service tenants).""" 5 """Provides info about projects (service tenants)."""
6 6
7 import logging 7 import logging
8 8
9 from google.appengine.api import memcache 9 from google.appengine.api import memcache
10 from google.appengine.ext import ndb 10 from google.appengine.ext import ndb
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
70 70
71 71
72 def get_project(id): 72 def get_project(id):
73 """Returns a project by id.""" 73 """Returns a project by id."""
74 for p in get_projects(): 74 for p in get_projects():
75 if p.id == id: 75 if p.id == id:
76 return p 76 return p
77 return None 77 return None
78 78
79 79
80 def get_repos(project_ids): 80 @ndb.tasklet
81 def get_repos_async(project_ids):
81 """Returns a mapping {project_id: (repo_type, repo_url)}. 82 """Returns a mapping {project_id: (repo_type, repo_url)}.
82 83
83 All projects must exist. 84 All projects must exist.
84 """ 85 """
85 assert isinstance(project_ids, list) 86 assert isinstance(project_ids, list)
86 keys = [ndb.Key(ProjectImportInfo, pid) for pid in project_ids] 87 infos = yield ndb.get_multi_async(
87 return { 88 ndb.Key(ProjectImportInfo, pid) for pid in project_ids)
89 raise ndb.Return({
88 pid: (info.repo_type, info.repo_url) if info else (None, None) 90 pid: (info.repo_type, info.repo_url) if info else (None, None)
89 for pid, info in zip(project_ids, ndb.get_multi(keys)) 91 for pid, info in zip(project_ids, infos)
90 } 92 })
91 93
92 94
93 def get_metadata(project_ids): 95 @ndb.tasklet
96 def get_metadata_async(project_ids):
94 """Returns a mapping {project_id: metadata}. 97 """Returns a mapping {project_id: metadata}.
95 98
96 If a project does not exist, the metadata is None. 99 If a project does not exist, the metadata is None.
97 100
98 The project metadata stored in project.cfg files in each project. 101 The project metadata stored in project.cfg files in each project.
99 """ 102 """
103 PROJECT_DOES_NOT_EXIST_SENTINEL = (0,)
100 cache_ns = 'projects.get_metadata' 104 cache_ns = 'projects.get_metadata'
101 cache_map = memcache.get_multi(project_ids, namespace=cache_ns) 105 ctx = ndb.get_context()
106 # ctx.memcache_get is auto-batching. Internally it makes get_multi RPC.
107 cache_futs = {
108 pid: ctx.memcache_get(pid, namespace=cache_ns)
109 for pid in project_ids
110 }
111 yield cache_futs.values()
102 result = {} 112 result = {}
103 missing = [] 113 missing = []
104 for pid in project_ids: 114 for pid in project_ids:
105 if pid in cache_map: 115 binary = cache_futs[pid].get_result()
116 if binary is not None:
106 # cache hit 117 # cache hit
107 binary = cache_map[pid] 118 if binary is PROJECT_DOES_NOT_EXIST_SENTINEL:
108 if binary is None:
109 # project does not exist
110 result[pid] = None 119 result[pid] = None
111 else: 120 else:
112 cfg = project_config_pb2.ProjectCfg() 121 cfg = project_config_pb2.ProjectCfg()
113 cfg.ParseFromString(binary) 122 cfg.ParseFromString(binary)
114 result[pid] = cfg 123 result[pid] = cfg
115 else: 124 else:
116 # cache miss 125 # cache miss
117 missing.append(pid) 126 missing.append(pid)
118 127
119 if missing: 128 if missing:
120 fetched = _get_project_configs( 129 fetched = yield _get_project_configs_async(
121 missing, common.PROJECT_METADATA_FILENAME, 130 missing, common.PROJECT_METADATA_FILENAME,
122 project_config_pb2.ProjectCfg) 131 project_config_pb2.ProjectCfg)
123 result.update(fetched) # at this point result must have all project ids 132 result.update(fetched) # at this point result must have all project ids
124 # Cache metadata for 10 min. In practice, it never changes. 133 # Cache metadata for 10 min. In practice, it never changes.
125 cache_map = { 134 # ctx.memcache_set is auto-batching. Internally it makes set_multi RPC.
126 pid: cfg.SerializeToString() if cfg else None 135 yield [
136 ctx.memcache_set(
137 pid,
138 cfg.SerializeToString() if cfg else PROJECT_DOES_NOT_EXIST_SENTINEL,
139 namespace=cache_ns,
140 time=60 * 10)
127 for pid, cfg in fetched.iteritems() 141 for pid, cfg in fetched.iteritems()
128 } 142 ]
129 memcache.set_multi(cache_map, namespace=cache_ns, time=60 * 10)
130 143
131 return result 144 raise ndb.Return(result)
132 145
133 146
134 def get_refs(project_ids): 147 def get_refs(project_ids):
135 """Returns a mapping {project_id: list of refs} 148 """Returns a mapping {project_id: list of refs}
136 149
137 The ref list is None if a project does not exist. 150 The ref list is None if a project does not exist.
138 151
139 The list of refs stored in refs.cfg of a project. 152 The list of refs stored in refs.cfg of a project.
140 """ 153 """
141 cfgs = _get_project_configs( 154 cfgs = _get_project_configs_async(
142 project_ids, common.REFS_FILENAME, project_config_pb2.RefsCfg) 155 project_ids, common.REFS_FILENAME, project_config_pb2.RefsCfg
156 ).get_result()
143 return { 157 return {
144 pid: None if cfg is None else cfg.refs or DEFAULT_REF_CFG.refs 158 pid: None if cfg is None else cfg.refs or DEFAULT_REF_CFG.refs
145 for pid, cfg in cfgs.iteritems() 159 for pid, cfg in cfgs.iteritems()
146 } 160 }
147 161
148 162
149 def _get_project_configs(project_ids, path, message_factory): 163 def _get_project_configs_async(project_ids, path, message_factory):
150 """Returns a mapping {project_id: message}. 164 """Returns a mapping {project_id: message}.
151 165
152 If a project does not exist, the message is None. 166 If a project does not exist, the message is None.
153 """ 167 """
154 assert isinstance(project_ids, list) 168 assert isinstance(project_ids, list)
155 if not project_ids: 169 if not project_ids:
156 return {} 170 return {}
157 prefix = 'projects/' 171
158 messages = storage.get_latest_messages_async( 172 @ndb.tasklet
159 [prefix + pid for pid in _filter_existing(project_ids)], 173 def get_async():
160 path, message_factory).get_result() 174 prefix = 'projects/'
161 return { 175 messages = yield storage.get_latest_messages_async(
162 # messages may not have a key because we filter project ids by existence 176 [prefix + pid for pid in _filter_existing(project_ids)],
163 pid: messages.get(prefix + pid) 177 path, message_factory)
164 for pid in project_ids 178 raise ndb.Return({
165 } 179 # messages may not have a key because we filter project ids by existence
180 pid: messages.get(prefix + pid)
181 for pid in project_ids
182 })
183
184 return get_async()
166 185
167 186
168 def _filter_existing(project_ids): 187 def _filter_existing(project_ids):
169 # TODO(nodir): optimize 188 # TODO(nodir): optimize
170 assert isinstance(project_ids, list) 189 assert isinstance(project_ids, list)
171 if not project_ids: 190 if not project_ids:
172 return project_ids 191 return project_ids
173 assert all(pid for pid in project_ids) 192 assert all(pid for pid in project_ids)
174 all_project_ids = set(p.id for p in get_projects()) 193 all_project_ids = set(p.id for p in get_projects())
175 return [ 194 return [
176 pid for pid in project_ids 195 pid for pid in project_ids
177 if pid in all_project_ids 196 if pid in all_project_ids
178 ] 197 ]
OLDNEW
« no previous file with comments | « appengine/config_service/api_test.py ('k') | appengine/config_service/projects_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698