Skip to content

Commit 88f1619

Browse files
authored
Merge pull request #45 from metno/update_docs
Update docs
2 parents e00d930 + c7aabbd commit 88f1619

6 files changed

Lines changed: 37 additions & 47 deletions

File tree

correctedforecaster/internal/server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ func (s *Server) correctWithBetterTopography(request *internalprotocol.GetForeca
148148
}
149149

150150
altitudeDiff := *modelAltitude - realAltitude
151+
// Only correct when the altitude difference exceeds ±100 m. Smaller gaps are
152+
// within the noise of the model grid and not worth adjusting.
151153
if altitudeDiff < -100 || altitudeDiff > 100 {
152154
correction.UpdateTemperature(interpreted, altitudeDiff)
153155
correction.UpdateDewpointTemperature(interpreted)

healthz/internal/health/probe.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func NewProbeResult(serviceProblems, dataProblems map[string][]string, maxfailed
4646
func runProbe(conf *config.ProbeConfiguration) ProbeResult {
4747
log.Println("Perform probe...")
4848

49+
// serviceProblems and dataProblems are tracked separately so that health can be
50+
// reported on two independent dimensions: service (any location failing means the
51+
// API itself is broken) vs. data (a configurable number of locations may have stale
52+
// or missing data before the data dimension is considered unhealthy).
4953
serviceProblems := map[string][]string{}
5054
dataProblems := map[string][]string{}
5155

jsonfrontend/internal/server/server.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
7979
metrics.OutsideAllGrids.Add(1)
8080
return
8181
case internalprotocol.ForecastStatus_PointTooFarAway:
82+
// The point is within a grid's bounding area but too far from any grid cell.
83+
// Return 200 with a GeoJSON error body rather than 4xx so that clients receive
84+
// a structured response with location context instead of a plain HTTP error.
8285
doc = encode.EncodeError(data, "no data at the given location")
8386
metrics.PointTooFarAway.Add(1)
8487
default:
@@ -193,7 +196,8 @@ func getParam(q url.Values, name string, from float32, to float32) (float32, err
193196
}
194197

195198
// expires returns a randomized time.Time for use as a value to the Expires header.
196-
// Maximum value is current + maxOffset seconds.
199+
// The expiry is randomly drawn from [0.8×maxOffset, maxOffset) rather than being
200+
// a fixed value. This spreads cache invalidation across clients to prevent stampedes.
197201
// Returns current time + 1 minute if maxOffset <=0.
198202
func expires(current time.Time, maxOffset int) time.Time {
199203
if maxOffset > 0 {

rawdataforecaster/README.md

Lines changed: 11 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,18 @@
1-
# Simple Forecaster
1+
# rawdataforecaster
22

3-
Serves unmodified data from a forecast. The term 'simple' refers to the data not having been processed by forti.
3+
Serves raw (uncorrected) forecast data over gRPC from a blob store (Azure, S3, or local `file://`) in the [forti-internalformat](https://github.com/metno/forti-internalformat) format.
44

5-
## Serving data
5+
## Loader strategies
66

7-
```mermaid
8-
graph LR;
9-
server --> forecast;
10-
forecast --> dataset;
11-
dataset --> index;
12-
dataset --> values;
13-
```
7+
The `loader.type` config key selects how forecast data is held in memory:
148

15-
Four components are mainly involved in serving data:
9+
| Type | Behaviour |
10+
|---|---|
11+
| `memory` | Downloads the full grid blob at startup into CGo-allocated memory (bypassing GC pressure). Zero I/O at query time. Capped by `max_size_gib`. |
12+
| `blob` | Downloads nothing upfront; issues a byte-range read per query (2 s timeout). |
1613

17-
### server
14+
Use `memory` when latency matters and the dataset fits in RAM; use `blob` otherwise.
1815

19-
Handles incoming grpc requests, including protobuf serialization.
16+
## Native dependencies
2017

21-
### forecast
22-
23-
Determines what is the correct group (and version) to serve data from. Forwards requests to the relevant `dataset` handler.
24-
25-
### dataset
26-
27-
Each object of type `dataset.Dataset` serves data for a single area/version. They maintain a list of grids for its area. A grid is a unique collection of latitude/longitude pairs within a single area. They exist because different parameters may have different grid resolutions.
28-
29-
Handles requests for a given latitute/longitude pair. For each grid, lookup the correct index from `index`, and find relevant data from `values`.
30-
31-
### index
32-
33-
Handles lookup from latitude/longitude to a grid index.
34-
35-
### values
36-
37-
A collection of all data having the same area and grid id.
38-
39-
Provides a `Reader` interface, for looking up data with a given index. The index is provided by the `geo` component. There are several implementations of this interface.
40-
41-
## Loading data
42-
43-
`forecast` component contains a function, `Forecast.update`, that is called periodically in a goroutine. It checks a blob store for updates, and loads data if needed, by calling `dataset.Download`.
44-
45-
## Other modules
46-
47-
### internal.health
48-
49-
Provides grpc healthcheck, meant for kubernetes readiness probe.
50-
51-
### pointdata
52-
53-
Defines internal data format. Its placement reflects that several modules in various places in the hierarchy needs access to this.
18+
The geographic index uses [s2geometry](https://github.com/google/s2geometry) and [PROJ](https://proj.org/) via CGo. These are pre-installed in the devcontainer; building outside it requires both libraries.

rawdataforecaster/cmd/lookup/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
func main() {
1616
latitude := flag.Float64("lat", 59, "latitude to query")
1717
longitude := flag.Float64("lon", 11, "longitude to query")
18+
altitude := flag.Float64("altitude", -1, "altitude to query; set to 0 for sea level, default -1 means not set")
1819
address := flag.String("address", "localhost:5052", "Server to connect to")
20+
parameter := flag.String("parameter", "", "Only show data for the given parameter")
1921
flag.Parse()
2022

2123
// Set up a connection to the server.
@@ -33,6 +35,12 @@ func main() {
3335
Latitude: float32(*latitude),
3436
Longitude: float32(*longitude),
3537
}
38+
if *altitude != -1 {
39+
request.Altitude = &internalprotocol.Altitude{
40+
Override: true,
41+
Value: float32(*altitude),
42+
}
43+
}
3644

3745
forecast, err := c.GetForecast(ctx, &request)
3846
if err != nil {
@@ -46,6 +54,9 @@ func main() {
4654
fmt.Printf("updated at: %v\n", forecast.ForecastMeta.UpdatedAt.AsTime())
4755
fmt.Printf("next update: %v\n", forecast.ForecastMeta.NextUpdate.AsTime())
4856
for _, meta := range forecast.ParameterMeta {
57+
if *parameter != "" && meta.Parameter != *parameter {
58+
continue
59+
}
4960
fmt.Printf("%s (%s):\n", meta.Parameter, meta.Units)
5061
for i, t := range meta.Times {
5162
value := forecast.Data[i+int(meta.SliceFrom)]

rawdataforecaster/internal/server/forecast/dataset/index/geo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ type indexID struct {
2929
GridID string
3030
}
3131

32+
// idCache maps a (area, version, gridID) triple to the MD5 checksum of its lat/lon data.
33+
// checkSumCache maps that checksum to a shared GeoMap (the s2geometry spatial index) with
34+
// a reference count. Together they allow datasets with identical grid geometry to reuse
35+
// the same GeoMap across version updates, avoiding an expensive rebuild.
3236
var idCache map[indexID]string
3337
var checkSumCache map[string]*cachedMaps
3438
var cacheMutex sync.Mutex

0 commit comments

Comments
 (0)