chiitiler 1.23.7 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/cache/front.d.ts +11 -0
- package/dist/cache/front.js +52 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/cli.js +8 -0
- package/dist/render/pool.js +2 -1
- package/dist/source/pmtiles.js +13 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,6 +170,8 @@ Prewarm is enabled by default: one tile is rendered at startup **before** the se
|
|
|
170
170
|
| `--cache <none\|memory\|file\|s3\|gcs>` | `CHIITILER_CACHE_METHOD` | `none` |
|
|
171
171
|
| `--cache-ttl <seconds>` | `CHIITILER_CACHE_TTL_SEC` | `3600` |
|
|
172
172
|
| `--memory-cache-max-item-count <n>` | `CHIITILER_MEMORYCACHE_MAXITEMCOUNT` | `1000` |
|
|
173
|
+
| — | `CHIITILER_FRONT_CACHE_TTL_SEC` | `10` (seconds) |
|
|
174
|
+
| — | `CHIITILER_FRONT_CACHE_MAX_BYTES` | `67108864` (64 MiB) |
|
|
173
175
|
| `--file-cache-dir <dir>` | `CHIITILER_FILECACHE_DIR` | `./.cache` |
|
|
174
176
|
| `--s3-cache-bucket <name>` | `CHIITILER_S3CACHE_BUCKET` | — |
|
|
175
177
|
| `--s3-region <region>` | `CHIITILER_S3_REGION` | `us-east-1` |
|
|
@@ -183,6 +185,33 @@ Prewarm is enabled by default: one tile is rendered at startup **before** the se
|
|
|
183
185
|
|
|
184
186
|
Chiitiler caches *source assets* (vector tiles, glyphs, sprites) — not final rasters — so cached data is reused across requests. Standard AWS / GCP credentials (`AWS_ACCESS_KEY_ID`, `GOOGLE_APPLICATION_CREDENTIALS`, etc.) are respected.
|
|
185
187
|
|
|
188
|
+
The tile server adds a per-process memory front cache to `file`, `s3`, and `gcs`:
|
|
189
|
+
by default, buffers are retained for 10 seconds from insertion, with an LRU limit of 64 MiB
|
|
190
|
+
of buffer payloads (JavaScript overhead is additional). Reads promote backing-cache
|
|
191
|
+
hits into memory; writes populate memory immediately and also write to the backing
|
|
192
|
+
cache. `none` and `memory` keep their existing behavior. This affects sources that
|
|
193
|
+
already use the cache, not direct `s3://`, `gs://`, or local source reads.
|
|
194
|
+
|
|
195
|
+
Set `CHIITILER_FRONT_CACHE_TTL_SEC` to a positive, finite number of seconds and
|
|
196
|
+
`CHIITILER_FRONT_CACHE_MAX_BYTES` to a positive integer number of bytes to override
|
|
197
|
+
these limits. Setting either variable to `0` disables the front cache and uses the
|
|
198
|
+
backing cache directly. Otherwise, invalid values stop startup when using `file`,
|
|
199
|
+
`s3`, or `gcs`.
|
|
200
|
+
These settings do not affect `none`, `memory`, or library callers.
|
|
201
|
+
|
|
202
|
+
The front-cache TTL does not guarantee source freshness: promotion can retain a
|
|
203
|
+
value for up to the configured TTL beyond its backing-cache expiry, and the S3/GCS cache
|
|
204
|
+
implementations do not check expiry on reads. Each worker has its own memory limit.
|
|
205
|
+
Library callers can opt in and customize the limits (either limit set to `0`
|
|
206
|
+
returns the backing cache unchanged):
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const cache = ChiitilerCache.withMemoryCache(
|
|
210
|
+
ChiitilerCache.fileCache({ dir: './.cache', ttl: 3600 }),
|
|
211
|
+
{ ttlSeconds: 10, maxBytes: 64 * 1024 * 1024 },
|
|
212
|
+
);
|
|
213
|
+
```
|
|
214
|
+
|
|
186
215
|
## Deployment
|
|
187
216
|
|
|
188
217
|
- **Docker** — `ghcr.io/kanahiro/chiitiler:latest` (entrypoint: `tile-server`)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Cache } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Keep recent source buffers in this process to avoid backing-cache I/O.
|
|
4
|
+
* TTL starts on insertion, so promotion can extend the backing cache's lifetime
|
|
5
|
+
* by up to ttlSeconds. maxBytes bounds buffer payloads, not total process memory.
|
|
6
|
+
* Either limit set to zero disables the front cache and returns backing unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export declare function withMemoryCache(backing: Cache, { ttlSeconds, maxBytes }?: {
|
|
9
|
+
ttlSeconds?: number | undefined;
|
|
10
|
+
maxBytes?: number | undefined;
|
|
11
|
+
}): Cache;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
/**
|
|
3
|
+
* Keep recent source buffers in this process to avoid backing-cache I/O.
|
|
4
|
+
* TTL starts on insertion, so promotion can extend the backing cache's lifetime
|
|
5
|
+
* by up to ttlSeconds. maxBytes bounds buffer payloads, not total process memory.
|
|
6
|
+
* Either limit set to zero disables the front cache and returns backing unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export function withMemoryCache(backing, { ttlSeconds = 10, maxBytes = 64 * 1024 * 1024 } = {}) {
|
|
9
|
+
if (ttlSeconds === 0 || maxBytes === 0)
|
|
10
|
+
return backing;
|
|
11
|
+
if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
|
|
12
|
+
throw new Error('ttlSeconds must be positive and finite');
|
|
13
|
+
}
|
|
14
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
15
|
+
throw new Error('maxBytes must be a positive safe integer');
|
|
16
|
+
}
|
|
17
|
+
const memory = new LRUCache({
|
|
18
|
+
maxSize: maxBytes,
|
|
19
|
+
sizeCalculation: (value) => Math.max(1, value.byteLength),
|
|
20
|
+
ttl: ttlSeconds * 1000,
|
|
21
|
+
ttlResolution: 0,
|
|
22
|
+
});
|
|
23
|
+
const reads = new Map();
|
|
24
|
+
return {
|
|
25
|
+
name: backing.name,
|
|
26
|
+
get: async (key) => {
|
|
27
|
+
const hit = memory.get(key);
|
|
28
|
+
if (hit !== undefined)
|
|
29
|
+
return hit;
|
|
30
|
+
const pending = reads.get(key);
|
|
31
|
+
if (pending !== undefined)
|
|
32
|
+
return pending;
|
|
33
|
+
const read = backing.get(key).then((value) => {
|
|
34
|
+
// A newer write must not be overwritten by an older read.
|
|
35
|
+
if (value !== undefined && reads.get(key) === read)
|
|
36
|
+
memory.set(key, value);
|
|
37
|
+
return value;
|
|
38
|
+
}).finally(() => {
|
|
39
|
+
if (reads.get(key) === read)
|
|
40
|
+
reads.delete(key);
|
|
41
|
+
});
|
|
42
|
+
reads.set(key, read);
|
|
43
|
+
return read;
|
|
44
|
+
},
|
|
45
|
+
set: async (key, value) => {
|
|
46
|
+
reads.delete(key);
|
|
47
|
+
// Populate before awaiting persistence; callers may not await set.
|
|
48
|
+
memory.set(key, value);
|
|
49
|
+
await backing.set(key, value);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
package/dist/cache/index.d.ts
CHANGED
package/dist/cache/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { memoryCache } from './memory.js';
|
|
|
2
2
|
import { s3Cache } from './s3.js';
|
|
3
3
|
import { gcsCache } from './gcs.js';
|
|
4
4
|
import { fileCache } from './file.js';
|
|
5
|
+
export { withMemoryCache } from './front.js';
|
|
5
6
|
const noneCache = () => ({
|
|
6
7
|
name: 'none',
|
|
7
8
|
get: async () => undefined,
|
package/dist/cli.js
CHANGED
|
@@ -130,6 +130,14 @@ export function createProgram() {
|
|
|
130
130
|
port: parsePort(options.port),
|
|
131
131
|
debug: parseDebug(options.debug),
|
|
132
132
|
};
|
|
133
|
+
if (['file', 's3', 'gcs'].includes(serverOptions.cache.name)) {
|
|
134
|
+
const ttl = process.env.CHIITILER_FRONT_CACHE_TTL_SEC;
|
|
135
|
+
const size = process.env.CHIITILER_FRONT_CACHE_MAX_BYTES;
|
|
136
|
+
serverOptions.cache = caches.withMemoryCache(serverOptions.cache, {
|
|
137
|
+
ttlSeconds: ttl === undefined ? undefined : Number(ttl.trim() || NaN),
|
|
138
|
+
maxBytes: size === undefined ? undefined : Number(size.trim() || NaN),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
133
141
|
if (serverOptions.debug) {
|
|
134
142
|
console.log(`running server: http://localhost:${serverOptions.port}`);
|
|
135
143
|
console.log(`cache method: ${serverOptions.cache.name}`);
|
package/dist/render/pool.js
CHANGED
|
@@ -12,7 +12,8 @@ const mapPoolCache = new LRUCache({
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
async function getRenderPool(style, cache, mode) {
|
|
15
|
-
|
|
15
|
+
// A Map's mode is fixed at creation; tile and static requests cannot share it.
|
|
16
|
+
const cacheKey = `${mode}:${JSON.stringify(style)}`;
|
|
16
17
|
const pool = mapPoolCache.get(cacheKey);
|
|
17
18
|
if (pool !== undefined)
|
|
18
19
|
return pool;
|
package/dist/source/pmtiles.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
-
import { PMTiles, FetchSource,
|
|
2
|
+
import { PMTiles, FetchSource, SharedPromiseCache, } from 'pmtiles';
|
|
3
3
|
import { GetObjectCommand, } from '@aws-sdk/client-s3';
|
|
4
4
|
import { LRUCache } from 'lru-cache';
|
|
5
5
|
import { getS3Client } from '../s3.js';
|
|
@@ -82,13 +82,15 @@ async function getPmtilesSource(uri, cache = noneCache()) {
|
|
|
82
82
|
const val = await cache.get(uri);
|
|
83
83
|
if (val !== undefined)
|
|
84
84
|
return val; // hit
|
|
85
|
+
// Another request may have created the archive while cache.get awaited.
|
|
86
|
+
pmtiles = pmtilesCache.get(pmtilesUri);
|
|
85
87
|
if (pmtiles === undefined) {
|
|
86
88
|
const userAgent = getUserAgent();
|
|
87
89
|
const headers = new Headers();
|
|
88
90
|
if (userAgent)
|
|
89
91
|
headers.set('User-Agent', userAgent);
|
|
90
92
|
const fetchSource = new FetchSource(pmtilesUri, headers);
|
|
91
|
-
pmtiles = new PMTiles(fetchSource, new
|
|
93
|
+
pmtiles = new PMTiles(fetchSource, new SharedPromiseCache());
|
|
92
94
|
pmtilesCache.set(pmtilesUri, pmtiles);
|
|
93
95
|
}
|
|
94
96
|
}
|
|
@@ -97,19 +99,25 @@ async function getPmtilesSource(uri, cache = noneCache()) {
|
|
|
97
99
|
const bucket = pmtilesUri.replace('s3://', '').split('/')[0];
|
|
98
100
|
const key = pmtilesUri.replace(`s3://${bucket}/`, '');
|
|
99
101
|
const s3Source = new S3Source(bucket, key);
|
|
100
|
-
pmtiles = new PMTiles(s3Source, new
|
|
102
|
+
pmtiles = new PMTiles(s3Source, new SharedPromiseCache());
|
|
101
103
|
pmtilesCache.set(pmtilesUri, pmtiles);
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
else {
|
|
105
107
|
if (pmtiles === undefined) {
|
|
106
108
|
const fileSource = new FilesystemSource(pmtilesUri);
|
|
107
|
-
pmtiles = new PMTiles(fileSource, new
|
|
109
|
+
pmtiles = new PMTiles(fileSource, new SharedPromiseCache());
|
|
108
110
|
pmtilesCache.set(pmtilesUri, pmtiles);
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
const [z, x, y] = uri.replace(`pmtiles://${pmtilesUri}/`, '').split('/');
|
|
112
|
-
const tile = await pmtiles.getZxy(Number(z), Number(x), Number(y))
|
|
114
|
+
const tile = await pmtiles.getZxy(Number(z), Number(x), Number(y)).catch((err) => {
|
|
115
|
+
// SharedPromiseCache retains rejected metadata promises. Rebuild after a
|
|
116
|
+
// failure, without evicting a replacement created by a newer request.
|
|
117
|
+
if (pmtilesCache.get(pmtilesUri) === pmtiles)
|
|
118
|
+
pmtilesCache.delete(pmtilesUri);
|
|
119
|
+
throw err;
|
|
120
|
+
});
|
|
113
121
|
if (!tile)
|
|
114
122
|
return null;
|
|
115
123
|
const buf = Buffer.from(tile.data);
|