Mercurial > gemma
comparison pkg/mesh/cache.go @ 4827:f4abfd0ee8ad remove-octree-debris
Renamed octree package to mesh as there is no octree any more.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Tue, 05 Nov 2019 14:30:22 +0100 |
parents | pkg/octree/cache.go@4bbfe3dd2ab5 |
children | 181c2c05b12a |
comparison
equal
deleted
inserted
replaced
4826:ec5afada70ec | 4827:f4abfd0ee8ad |
---|---|
1 // This is Free Software under GNU Affero General Public License v >= 3.0 | |
2 // without warranty, see README.md and license for details. | |
3 // | |
4 // SPDX-License-Identifier: AGPL-3.0-or-later | |
5 // License-Filename: LICENSES/AGPL-3.0.txt | |
6 // | |
7 // Copyright (C) 2018 by via donau | |
8 // – Österreichische Wasserstraßen-Gesellschaft mbH | |
9 // Software engineering by Intevation GmbH | |
10 // | |
11 // Author(s): | |
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> | |
13 | |
14 package mesh | |
15 | |
16 import ( | |
17 "context" | |
18 "database/sql" | |
19 "sync" | |
20 "time" | |
21 ) | |
22 | |
23 type ( | |
24 cacheKey struct { | |
25 date time.Time | |
26 bottleneck string | |
27 } | |
28 | |
29 cacheEntry struct { | |
30 checksum string | |
31 tree *STRTree | |
32 access time.Time | |
33 } | |
34 | |
35 // Cache holds Octrees for a defined amount of time in memory | |
36 // before they are released. | |
37 Cache struct { | |
38 sync.Mutex | |
39 entries map[cacheKey]*cacheEntry | |
40 } | |
41 ) | |
42 | |
43 const ( | |
44 cleanupCacheSleep = 6 * time.Minute | |
45 maxCacheAge = 5 * time.Minute | |
46 maxCacheEntries = 4 | |
47 ) | |
48 | |
49 const ( | |
50 directMeshSQL = ` | |
51 SELECT mesh_index FROM waterway.sounding_results | |
52 WHERE id = $1 | |
53 ` | |
54 fetchMeshSQL = ` | |
55 SELECT mesh_checksum, mesh_index | |
56 FROM waterway.sounding_results | |
57 WHERE bottleneck_id = $1 AND date_info = $2::date | |
58 AND mesh_checksum IS NOT NULL AND mesh_index IS NOT NULL | |
59 ` | |
60 checkMeshSQL = ` | |
61 SELECT CASE | |
62 WHEN mesh_checksum = $3 THEN NULL | |
63 ELSE mesh_index | |
64 END | |
65 FROM waterway.sounding_results | |
66 WHERE bottleneck_id = $1 AND date_info = $2::date | |
67 AND mesh_checksum IS NOT NULL AND mesh_index IS NOT NULL | |
68 ` | |
69 ) | |
70 | |
71 var cache = Cache{ | |
72 entries: map[cacheKey]*cacheEntry{}, | |
73 } | |
74 | |
75 func init() { | |
76 go cache.background() | |
77 } | |
78 | |
79 func (c *Cache) background() { | |
80 for { | |
81 time.Sleep(cleanupCacheSleep) | |
82 c.cleanup() | |
83 } | |
84 } | |
85 | |
86 func (c *Cache) cleanup() { | |
87 c.Lock() | |
88 defer c.Unlock() | |
89 good := time.Now().Add(-maxCacheAge) | |
90 for k, v := range c.entries { | |
91 if v.access.Before(good) { | |
92 delete(c.entries, k) | |
93 } | |
94 } | |
95 } | |
96 | |
97 // FromCache fetches an Octree from the global Octree cache. | |
98 func FromCache( | |
99 ctx context.Context, | |
100 conn *sql.Conn, | |
101 bottleneck string, date time.Time, | |
102 ) (*STRTree, error) { | |
103 return cache.get(ctx, conn, bottleneck, date) | |
104 } | |
105 | |
106 // FetchOctreeDirectly loads an octree directly from the database. | |
107 func FetchMeshDirectly( | |
108 ctx context.Context, | |
109 tx *sql.Tx, | |
110 id int64, | |
111 ) (*STRTree, error) { | |
112 var data []byte | |
113 err := tx.QueryRowContext(ctx, directMeshSQL, id).Scan(&data) | |
114 if err != nil { | |
115 return nil, err | |
116 } | |
117 tree := new(STRTree) | |
118 if err := tree.FromBytes(data); err != nil { | |
119 return nil, err | |
120 } | |
121 return tree, nil | |
122 } | |
123 | |
124 func (c *Cache) get( | |
125 ctx context.Context, | |
126 conn *sql.Conn, | |
127 bottleneck string, date time.Time, | |
128 ) (*STRTree, error) { | |
129 c.Lock() | |
130 defer c.Unlock() | |
131 | |
132 key := cacheKey{date, bottleneck} | |
133 entry := c.entries[key] | |
134 | |
135 var data []byte | |
136 var checksum string | |
137 | |
138 if entry == nil { | |
139 // fetch from database | |
140 err := conn.QueryRowContext( | |
141 ctx, fetchMeshSQL, bottleneck, date).Scan(&checksum, &data) | |
142 switch { | |
143 case err == sql.ErrNoRows: | |
144 return nil, nil | |
145 case err != nil: | |
146 return nil, err | |
147 } | |
148 } else { | |
149 // check if we are not outdated. | |
150 err := conn.QueryRowContext( | |
151 ctx, checkMeshSQL, bottleneck, date, entry.checksum).Scan(&data) | |
152 switch { | |
153 case err == sql.ErrNoRows: | |
154 return nil, nil | |
155 case err != nil: | |
156 return nil, err | |
157 } | |
158 if data == nil { // we are still current | |
159 entry.access = time.Now() | |
160 return entry.tree, nil | |
161 } | |
162 } | |
163 | |
164 tree := new(STRTree) | |
165 | |
166 if err := tree.FromBytes(data); err != nil { | |
167 return nil, err | |
168 } | |
169 | |
170 now := time.Now() | |
171 | |
172 if entry != nil { | |
173 entry.tree = tree | |
174 entry.access = now | |
175 return tree, nil | |
176 } | |
177 | |
178 for len(c.entries) >= maxCacheEntries { | |
179 // Evict the entry that is accessed the longest time ago. | |
180 var oldestKey cacheKey | |
181 oldest := now | |
182 | |
183 for k, v := range c.entries { | |
184 if v.access.Before(oldest) { | |
185 oldest = v.access | |
186 oldestKey = k | |
187 } | |
188 } | |
189 delete(c.entries, oldestKey) | |
190 } | |
191 | |
192 c.entries[key] = &cacheEntry{ | |
193 checksum: checksum, | |
194 tree: tree, | |
195 access: now, | |
196 } | |
197 | |
198 return tree, nil | |
199 } |