chiitiler 1.23.8 → 1.24.1
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 +34 -3
- 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 +33 -6
- package/dist/main.js +4 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,10 +158,12 @@ All options can be set via CLI flag or environment variable.
|
|
|
158
158
|
| `--port <n>` | `CHIITILER_PORT` | `3000` |
|
|
159
159
|
| `--debug` | `CHIITILER_DEBUG` | `false` |
|
|
160
160
|
| `--user-agent <ua>` | `CHIITILER_USER_AGENT` | (none) |
|
|
161
|
-
|
|
|
162
|
-
|
|
|
161
|
+
| `--prewarm [true\|false]` | `CHIITILER_PREWARM` | `true` |
|
|
162
|
+
| `--processes <n>` | `CHIITILER_PROCESSES` | `1` (set `0` for all CPUs) |
|
|
163
163
|
|
|
164
|
-
Prewarm is enabled by default: one tile is rendered at startup **before** the server starts listening, moving shared renderer initialization work into startup.
|
|
164
|
+
Prewarm is enabled by default: one tile is rendered at startup **before** the server starts listening, moving shared renderer initialization work into startup. `--prewarm false` or `CHIITILER_PREWARM=false` disables it. `--prewarm` alone enables it. The CLI flag takes precedence over the environment variable. Style-specific tiles, glyphs, and sprites are not preloaded.
|
|
165
|
+
|
|
166
|
+
`--processes` takes precedence over `CHIITILER_PROCESSES` and accepts non-negative integers; `0` uses all available CPUs.
|
|
165
167
|
|
|
166
168
|
### Cache
|
|
167
169
|
|
|
@@ -170,6 +172,8 @@ Prewarm is enabled by default: one tile is rendered at startup **before** the se
|
|
|
170
172
|
| `--cache <none\|memory\|file\|s3\|gcs>` | `CHIITILER_CACHE_METHOD` | `none` |
|
|
171
173
|
| `--cache-ttl <seconds>` | `CHIITILER_CACHE_TTL_SEC` | `3600` |
|
|
172
174
|
| `--memory-cache-max-item-count <n>` | `CHIITILER_MEMORYCACHE_MAXITEMCOUNT` | `1000` |
|
|
175
|
+
| `--front-cache-ttl-sec <seconds>` | `CHIITILER_FRONT_CACHE_TTL_SEC` | `10` (seconds) |
|
|
176
|
+
| `--front-cache-max-bytes <bytes>` | `CHIITILER_FRONT_CACHE_MAX_BYTES` | `67108864` (64 MiB) |
|
|
173
177
|
| `--file-cache-dir <dir>` | `CHIITILER_FILECACHE_DIR` | `./.cache` |
|
|
174
178
|
| `--s3-cache-bucket <name>` | `CHIITILER_S3CACHE_BUCKET` | — |
|
|
175
179
|
| `--s3-region <region>` | `CHIITILER_S3_REGION` | `us-east-1` |
|
|
@@ -183,6 +187,33 @@ Prewarm is enabled by default: one tile is rendered at startup **before** the se
|
|
|
183
187
|
|
|
184
188
|
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
189
|
|
|
190
|
+
The tile server adds a per-process memory front cache to `file`, `s3`, and `gcs`:
|
|
191
|
+
by default, buffers are retained for 10 seconds from insertion, with an LRU limit of 64 MiB
|
|
192
|
+
of buffer payloads (JavaScript overhead is additional). Reads promote backing-cache
|
|
193
|
+
hits into memory; writes populate memory immediately and also write to the backing
|
|
194
|
+
cache. `none` and `memory` keep their existing behavior. This affects sources that
|
|
195
|
+
already use the cache, not direct `s3://`, `gs://`, or local source reads.
|
|
196
|
+
|
|
197
|
+
Set `--front-cache-ttl-sec` / `CHIITILER_FRONT_CACHE_TTL_SEC` to a positive, finite number of seconds and
|
|
198
|
+
`--front-cache-max-bytes` / `CHIITILER_FRONT_CACHE_MAX_BYTES` to a positive integer number of bytes to override
|
|
199
|
+
these limits. CLI flags take precedence over environment variables. Setting either limit to `0` disables the front cache and uses the
|
|
200
|
+
backing cache directly. Otherwise, invalid values stop startup when using `file`,
|
|
201
|
+
`s3`, or `gcs`.
|
|
202
|
+
These settings do not affect `none`, `memory`, or library callers.
|
|
203
|
+
|
|
204
|
+
The front-cache TTL does not guarantee source freshness: promotion can retain a
|
|
205
|
+
value for up to the configured TTL beyond its backing-cache expiry, and the S3/GCS cache
|
|
206
|
+
implementations do not check expiry on reads. Each worker has its own memory limit.
|
|
207
|
+
Library callers can opt in and customize the limits (either limit set to `0`
|
|
208
|
+
returns the backing cache unchanged):
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const cache = ChiitilerCache.withMemoryCache(
|
|
212
|
+
ChiitilerCache.fileCache({ dir: './.cache', ttl: 3600 }),
|
|
213
|
+
{ ttlSeconds: 10, maxBytes: 64 * 1024 * 1024 },
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
186
217
|
## Deployment
|
|
187
218
|
|
|
188
219
|
- **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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
|
-
import
|
|
2
|
+
import cluster from 'node:cluster';
|
|
3
|
+
import { availableParallelism } from 'node:os';
|
|
4
|
+
import { Command, Option, InvalidArgumentError } from 'commander';
|
|
3
5
|
import { initServer } from './server/index.js';
|
|
4
6
|
import * as caches from './cache/index.js';
|
|
5
7
|
import { setUserAgent } from './source/userAgent.js';
|
|
@@ -72,9 +74,12 @@ function parsePort(port) {
|
|
|
72
74
|
// undefined or invalid
|
|
73
75
|
return 3000;
|
|
74
76
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
function parseProcesses(value) {
|
|
78
|
+
const count = Number(value.trim() || NaN);
|
|
79
|
+
if (!Number.isSafeInteger(count) || count < 0) {
|
|
80
|
+
throw new InvalidArgumentError('processes must be a non-negative safe integer');
|
|
81
|
+
}
|
|
82
|
+
return count;
|
|
78
83
|
}
|
|
79
84
|
function parseDebug(debug) {
|
|
80
85
|
// command-line option
|
|
@@ -106,9 +111,23 @@ export function createProgram() {
|
|
|
106
111
|
.option('--gcs-api-endpoint <api-endpoint>', 'gcs api endpoint', '')
|
|
107
112
|
.option('-p, --port <port>', 'port number')
|
|
108
113
|
.option('--user-agent <user-agent>', 'User-Agent header for outbound HTTP requests')
|
|
109
|
-
.
|
|
114
|
+
.addOption(new Option('--prewarm [boolean]', 'startup renderer warmup')
|
|
115
|
+
.choices(['true', 'false']).preset('true').env('CHIITILER_PREWARM').default('true'))
|
|
116
|
+
.addOption(new Option('--processes <n>', 'worker processes (0 for all CPUs)')
|
|
117
|
+
.env('CHIITILER_PROCESSES').argParser(parseProcesses).default(1))
|
|
118
|
+
.addOption(new Option('--front-cache-ttl-sec <seconds>', 'memory front cache TTL (0 to disable)')
|
|
119
|
+
.env('CHIITILER_FRONT_CACHE_TTL_SEC'))
|
|
120
|
+
.addOption(new Option('--front-cache-max-bytes <bytes>', 'memory front cache byte limit (0 to disable)')
|
|
121
|
+
.env('CHIITILER_FRONT_CACHE_MAX_BYTES'))
|
|
110
122
|
.option('-D, --debug', 'debug mode')
|
|
111
123
|
.action(async (options) => {
|
|
124
|
+
const numProcesses = options.processes === 0 ? availableParallelism() : options.processes;
|
|
125
|
+
if (cluster.isPrimary && numProcesses !== 1) {
|
|
126
|
+
// Workers inherit argv and env, and initialize their own renderer and cache.
|
|
127
|
+
for (let i = 0; i < numProcesses; i++)
|
|
128
|
+
cluster.fork();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
112
131
|
// env fallback (CHIITILER_USER_AGENT) is handled in userAgent.ts
|
|
113
132
|
if (options.userAgent !== undefined)
|
|
114
133
|
setUserAgent(options.userAgent);
|
|
@@ -130,6 +149,14 @@ export function createProgram() {
|
|
|
130
149
|
port: parsePort(options.port),
|
|
131
150
|
debug: parseDebug(options.debug),
|
|
132
151
|
};
|
|
152
|
+
if (['file', 's3', 'gcs'].includes(serverOptions.cache.name)) {
|
|
153
|
+
const ttl = options.frontCacheTtlSec;
|
|
154
|
+
const size = options.frontCacheMaxBytes;
|
|
155
|
+
serverOptions.cache = caches.withMemoryCache(serverOptions.cache, {
|
|
156
|
+
ttlSeconds: ttl === undefined ? undefined : Number(ttl.trim() || NaN),
|
|
157
|
+
maxBytes: size === undefined ? undefined : Number(size.trim() || NaN),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
133
160
|
if (serverOptions.debug) {
|
|
134
161
|
console.log(`running server: http://localhost:${serverOptions.port}`);
|
|
135
162
|
console.log(`cache method: ${serverOptions.cache.name}`);
|
|
@@ -139,7 +166,7 @@ export function createProgram() {
|
|
|
139
166
|
// warm up BEFORE listening: Lambda Web Adapter polls the
|
|
140
167
|
// readiness check until the port opens, so work done here
|
|
141
168
|
// stays inside the INIT phase (full CPU boost)
|
|
142
|
-
if (
|
|
169
|
+
if (options.prewarm === 'true') {
|
|
143
170
|
const startedAt = Date.now();
|
|
144
171
|
try {
|
|
145
172
|
await prewarm(serverOptions.cache);
|
package/dist/main.js
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import cluster from 'node:cluster';
|
|
2
|
-
import { availableParallelism } from 'node:os';
|
|
3
1
|
import process from 'node:process';
|
|
4
2
|
import { createProgram } from './cli.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const processesEnv = Number(process.env.CHIITILER_PROCESSES);
|
|
10
|
-
if (processesEnv === 0)
|
|
11
|
-
return availableParallelism();
|
|
12
|
-
else
|
|
13
|
-
return processesEnv;
|
|
14
|
-
})();
|
|
15
|
-
if (cluster.isPrimary && numProcesses !== 1) {
|
|
16
|
-
// Fork workers.
|
|
17
|
-
for (let i = 0; i < numProcesses; i++)
|
|
18
|
-
cluster.fork();
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
program.parse(process.argv);
|
|
22
|
-
}
|
|
3
|
+
createProgram().parseAsync(process.argv).catch((error) => {
|
|
4
|
+
console.error(error);
|
|
5
|
+
process.exitCode = 1;
|
|
6
|
+
});
|