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 }