diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/mesh/cache.go	Tue Nov 05 14:30:22 2019 +0100
@@ -0,0 +1,199 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package mesh
+
+import (
+	"context"
+	"database/sql"
+	"sync"
+	"time"
+)
+
+type (
+	cacheKey struct {
+		date       time.Time
+		bottleneck string
+	}
+
+	cacheEntry struct {
+		checksum string
+		tree     *STRTree
+		access   time.Time
+	}
+
+	// Cache holds Octrees for a defined amount of time in memory
+	// before they are released.
+	Cache struct {
+		sync.Mutex
+		entries map[cacheKey]*cacheEntry
+	}
+)
+
+const (
+	cleanupCacheSleep = 6 * time.Minute
+	maxCacheAge       = 5 * time.Minute
+	maxCacheEntries   = 4
+)
+
+const (
+	directMeshSQL = `
+SELECT mesh_index FROM waterway.sounding_results
+WHERE id = $1
+`
+	fetchMeshSQL = `
+SELECT mesh_checksum, mesh_index
+FROM waterway.sounding_results
+WHERE bottleneck_id = $1 AND date_info = $2::date
+  AND mesh_checksum IS NOT NULL AND mesh_index IS NOT NULL
+`
+	checkMeshSQL = `
+SELECT CASE
+  WHEN mesh_checksum = $3 THEN NULL
+  ELSE mesh_index
+  END
+FROM waterway.sounding_results
+WHERE bottleneck_id = $1 AND date_info = $2::date
+  AND mesh_checksum IS NOT NULL AND mesh_index IS NOT NULL
+`
+)
+
+var cache = Cache{
+	entries: map[cacheKey]*cacheEntry{},
+}
+
+func init() {
+	go cache.background()
+}
+
+func (c *Cache) background() {
+	for {
+		time.Sleep(cleanupCacheSleep)
+		c.cleanup()
+	}
+}
+
+func (c *Cache) cleanup() {
+	c.Lock()
+	defer c.Unlock()
+	good := time.Now().Add(-maxCacheAge)
+	for k, v := range c.entries {
+		if v.access.Before(good) {
+			delete(c.entries, k)
+		}
+	}
+}
+
+// FromCache fetches an Octree from the global Octree cache.
+func FromCache(
+	ctx context.Context,
+	conn *sql.Conn,
+	bottleneck string, date time.Time,
+) (*STRTree, error) {
+	return cache.get(ctx, conn, bottleneck, date)
+}
+
+// FetchOctreeDirectly loads an octree directly from the database.
+func FetchMeshDirectly(
+	ctx context.Context,
+	tx *sql.Tx,
+	id int64,
+) (*STRTree, error) {
+	var data []byte
+	err := tx.QueryRowContext(ctx, directMeshSQL, id).Scan(&data)
+	if err != nil {
+		return nil, err
+	}
+	tree := new(STRTree)
+	if err := tree.FromBytes(data); err != nil {
+		return nil, err
+	}
+	return tree, nil
+}
+
+func (c *Cache) get(
+	ctx context.Context,
+	conn *sql.Conn,
+	bottleneck string, date time.Time,
+) (*STRTree, error) {
+	c.Lock()
+	defer c.Unlock()
+
+	key := cacheKey{date, bottleneck}
+	entry := c.entries[key]
+
+	var data []byte
+	var checksum string
+
+	if entry == nil {
+		// fetch from database
+		err := conn.QueryRowContext(
+			ctx, fetchMeshSQL, bottleneck, date).Scan(&checksum, &data)
+		switch {
+		case err == sql.ErrNoRows:
+			return nil, nil
+		case err != nil:
+			return nil, err
+		}
+	} else {
+		// check if we are not outdated.
+		err := conn.QueryRowContext(
+			ctx, checkMeshSQL, bottleneck, date, entry.checksum).Scan(&data)
+		switch {
+		case err == sql.ErrNoRows:
+			return nil, nil
+		case err != nil:
+			return nil, err
+		}
+		if data == nil { // we are still current
+			entry.access = time.Now()
+			return entry.tree, nil
+		}
+	}
+
+	tree := new(STRTree)
+
+	if err := tree.FromBytes(data); err != nil {
+		return nil, err
+	}
+
+	now := time.Now()
+
+	if entry != nil {
+		entry.tree = tree
+		entry.access = now
+		return tree, nil
+	}
+
+	for len(c.entries) >= maxCacheEntries {
+		// Evict the entry that is accessed the longest time ago.
+		var oldestKey cacheKey
+		oldest := now
+
+		for k, v := range c.entries {
+			if v.access.Before(oldest) {
+				oldest = v.access
+				oldestKey = k
+			}
+		}
+		delete(c.entries, oldestKey)
+	}
+
+	c.entries[key] = &cacheEntry{
+		checksum: checksum,
+		tree:     tree,
+		access:   now,
+	}
+
+	return tree, nil
+}