@malloy-publisher/server 0.0.212 → 0.0.214
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/dist/app/api-doc.yaml +23 -0
- package/dist/instrumentation.mjs +10 -2
- package/dist/package_load_worker.mjs +11 -1
- package/dist/server.mjs +60 -52
- package/package.json +1 -1
- package/src/controller/package.controller.ts +3 -3
- package/src/heap_check.ts +2 -2
- package/src/instrumentation.ts +2 -2
- package/src/materialization_metrics.ts +7 -7
- package/src/package_load/package_load_pool.ts +2 -2
- package/src/package_load/package_load_worker.ts +7 -1
- package/src/package_load/protocol.ts +4 -3
- package/src/query_cap_metrics.ts +3 -2
- package/src/query_concurrency.ts +3 -2
- package/src/query_timeout.ts +3 -2
- package/src/server.ts +7 -9
- package/src/service/build_plan.ts +4 -4
- package/src/service/connection_config.ts +4 -4
- package/src/service/db_utils.spec.ts +53 -1
- package/src/service/db_utils.ts +14 -1
- package/src/service/environment.ts +11 -9
- package/src/service/model.ts +12 -46
- package/src/service/package.ts +3 -2
- package/src/service/package_manifest.spec.ts +38 -0
- package/src/service/package_manifest.ts +28 -0
- package/src/service/package_memory_governor.ts +3 -3
- package/src/service/query_text.spec.ts +79 -0
- package/src/service/query_text.ts +43 -0
- package/src/service/source_extraction.ts +3 -4
- package/src/telemetry.ts +20 -0
package/dist/app/api-doc.yaml
CHANGED
|
@@ -2436,6 +2436,14 @@ components:
|
|
|
2436
2436
|
URI (gs:// or s3://) of the externally-computed manifest for this package.
|
|
2437
2437
|
On (re)load the publisher reads it and binds persist references
|
|
2438
2438
|
(buildId -> physicalTableName). Null = serve live.
|
|
2439
|
+
materialization:
|
|
2440
|
+
oneOf:
|
|
2441
|
+
- $ref: "#/components/schemas/PackageMaterializationConfig"
|
|
2442
|
+
- type: "null"
|
|
2443
|
+
description: |
|
|
2444
|
+
Package-level Malloy Persistence policy declared in
|
|
2445
|
+
malloy-publisher.json. The control plane reads it to drive scheduled
|
|
2446
|
+
re-materialization. Null/absent = no package-level policy.
|
|
2439
2447
|
manifestBindingStatus:
|
|
2440
2448
|
type: string
|
|
2441
2449
|
readOnly: true
|
|
@@ -2485,6 +2493,21 @@ components:
|
|
|
2485
2493
|
package is compiled; null only when the package declares no persist
|
|
2486
2494
|
source.
|
|
2487
2495
|
|
|
2496
|
+
PackageMaterializationConfig:
|
|
2497
|
+
type: object
|
|
2498
|
+
description: >-
|
|
2499
|
+
Package-level Malloy Persistence policy from malloy-publisher.json's
|
|
2500
|
+
`materialization` block. Surfaced verbatim so the control plane can drive
|
|
2501
|
+
scheduled version-level re-materialization without re-reading the package
|
|
2502
|
+
files.
|
|
2503
|
+
properties:
|
|
2504
|
+
schedule:
|
|
2505
|
+
type: ["string", "null"]
|
|
2506
|
+
description: >-
|
|
2507
|
+
5-field UNIX cron controlling how often the control plane
|
|
2508
|
+
re-materializes this package's published versions. Null/absent = no
|
|
2509
|
+
scheduled re-materialization (publish / on-demand only).
|
|
2510
|
+
|
|
2488
2511
|
Model:
|
|
2489
2512
|
type: object
|
|
2490
2513
|
description: Malloy model metadata and status information
|
package/dist/instrumentation.mjs
CHANGED
|
@@ -115174,7 +115174,6 @@ var init_logger = __esm(() => {
|
|
|
115174
115174
|
|
|
115175
115175
|
// src/instrumentation.ts
|
|
115176
115176
|
init_logger();
|
|
115177
|
-
var import_api = __toESM(require_src(), 1);
|
|
115178
115177
|
var import_auto_instrumentations_node = __toESM(require_src59(), 1);
|
|
115179
115178
|
var import_exporter_logs_otlp_proto = __toESM(require_src66(), 1);
|
|
115180
115179
|
var import_exporter_prometheus = __toESM(require_src71(), 1);
|
|
@@ -115186,6 +115185,15 @@ var import_sdk_node = __toESM(require_src106(), 1);
|
|
|
115186
115185
|
var import_sdk_trace_base = __toESM(require_src82(), 1);
|
|
115187
115186
|
var import_semantic_conventions = __toESM(require_src2(), 1);
|
|
115188
115187
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
115188
|
+
|
|
115189
|
+
// src/telemetry.ts
|
|
115190
|
+
var import_api = __toESM(require_src(), 1);
|
|
115191
|
+
var METER_NAME = "publisher";
|
|
115192
|
+
function publisherMeter() {
|
|
115193
|
+
return import_api.metrics.getMeter(METER_NAME);
|
|
115194
|
+
}
|
|
115195
|
+
|
|
115196
|
+
// src/instrumentation.ts
|
|
115189
115197
|
var prometheusExporter = null;
|
|
115190
115198
|
var sdk = null;
|
|
115191
115199
|
function getPrometheusMetricsHandler() {
|
|
@@ -115243,7 +115251,7 @@ function instrument() {
|
|
|
115243
115251
|
}
|
|
115244
115252
|
}
|
|
115245
115253
|
instrument();
|
|
115246
|
-
var meter =
|
|
115254
|
+
var meter = publisherMeter();
|
|
115247
115255
|
var httpRequestDuration = meter.createHistogram("http_server_request_duration_ms", {
|
|
115248
115256
|
description: "Duration of HTTP requests in milliseconds",
|
|
115249
115257
|
unit: "ms",
|
|
@@ -11712,6 +11712,15 @@ function unwrapQuotedExpression(body) {
|
|
|
11712
11712
|
return expr;
|
|
11713
11713
|
}
|
|
11714
11714
|
|
|
11715
|
+
// src/service/package_manifest.ts
|
|
11716
|
+
function parsePackageMaterialization(raw) {
|
|
11717
|
+
if (!raw || typeof raw !== "object") {
|
|
11718
|
+
return null;
|
|
11719
|
+
}
|
|
11720
|
+
const schedule = raw.schedule;
|
|
11721
|
+
return { schedule: typeof schedule === "string" ? schedule : null };
|
|
11722
|
+
}
|
|
11723
|
+
|
|
11715
11724
|
// src/service/source_extraction.ts
|
|
11716
11725
|
import {
|
|
11717
11726
|
isSourceDef
|
|
@@ -12235,7 +12244,8 @@ async function readPackageMetadata(packagePath) {
|
|
|
12235
12244
|
description: parsed.description,
|
|
12236
12245
|
explores: Array.isArray(parsed.explores) ? parsed.explores.map(normalizeModelPath) : undefined,
|
|
12237
12246
|
queryableSources: parsed.queryableSources === "all" ? "all" : "declared",
|
|
12238
|
-
manifestLocation: typeof parsed.manifestLocation === "string" ? parsed.manifestLocation : null
|
|
12247
|
+
manifestLocation: typeof parsed.manifestLocation === "string" ? parsed.manifestLocation : null,
|
|
12248
|
+
materialization: parsePackageMaterialization(parsed.materialization)
|
|
12239
12249
|
};
|
|
12240
12250
|
}
|
|
12241
12251
|
async function listPackageFiles(packagePath) {
|
package/dist/server.mjs
CHANGED
|
@@ -218980,7 +218980,6 @@ var require_lunr = __commonJS((exports, module) => {
|
|
|
218980
218980
|
|
|
218981
218981
|
// src/instrumentation.ts
|
|
218982
218982
|
init_logger();
|
|
218983
|
-
var import_api = __toESM(require_src(), 1);
|
|
218984
218983
|
var import_auto_instrumentations_node = __toESM(require_src59(), 1);
|
|
218985
218984
|
var import_exporter_logs_otlp_proto = __toESM(require_src66(), 1);
|
|
218986
218985
|
var import_exporter_prometheus = __toESM(require_src71(), 1);
|
|
@@ -218992,6 +218991,15 @@ var import_sdk_node = __toESM(require_src106(), 1);
|
|
|
218992
218991
|
var import_sdk_trace_base = __toESM(require_src82(), 1);
|
|
218993
218992
|
var import_semantic_conventions = __toESM(require_src2(), 1);
|
|
218994
218993
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
218994
|
+
|
|
218995
|
+
// src/telemetry.ts
|
|
218996
|
+
var import_api = __toESM(require_src(), 1);
|
|
218997
|
+
var METER_NAME = "publisher";
|
|
218998
|
+
function publisherMeter() {
|
|
218999
|
+
return import_api.metrics.getMeter(METER_NAME);
|
|
219000
|
+
}
|
|
219001
|
+
|
|
219002
|
+
// src/instrumentation.ts
|
|
218995
219003
|
var prometheusExporter = null;
|
|
218996
219004
|
var sdk = null;
|
|
218997
219005
|
function getPrometheusMetricsHandler() {
|
|
@@ -219049,7 +219057,7 @@ function instrument() {
|
|
|
219049
219057
|
}
|
|
219050
219058
|
}
|
|
219051
219059
|
instrument();
|
|
219052
|
-
var meter =
|
|
219060
|
+
var meter = publisherMeter();
|
|
219053
219061
|
var httpRequestDuration = meter.createHistogram("http_server_request_duration_ms", {
|
|
219054
219062
|
description: "Duration of HTTP requests in milliseconds",
|
|
219055
219063
|
unit: "ms",
|
|
@@ -224473,14 +224481,13 @@ var getProcessedPublisherConfig = (serverRoot) => {
|
|
|
224473
224481
|
init_errors();
|
|
224474
224482
|
|
|
224475
224483
|
// src/query_cap_metrics.ts
|
|
224476
|
-
var import_api2 = __toESM(require_src(), 1);
|
|
224477
224484
|
var capExceededCounter = null;
|
|
224478
224485
|
var configGaugesInstalled = false;
|
|
224479
224486
|
function ensureCapTelemetry() {
|
|
224480
224487
|
if (capExceededCounter && configGaugesInstalled) {
|
|
224481
224488
|
return capExceededCounter;
|
|
224482
224489
|
}
|
|
224483
|
-
const meter2 =
|
|
224490
|
+
const meter2 = publisherMeter();
|
|
224484
224491
|
if (!capExceededCounter) {
|
|
224485
224492
|
capExceededCounter = meter2.createCounter("publisher_query_cap_exceeded_total", {
|
|
224486
224493
|
description: "Queries rejected with 413 because the row or byte cap was exceeded. Labels: cap_type ('rows'|'bytes'), source ('connection_sql'|'model_query'|'notebook_cell')."
|
|
@@ -224518,7 +224525,6 @@ function recordQueryCapExceeded(capType, source) {
|
|
|
224518
224525
|
init_logger();
|
|
224519
224526
|
|
|
224520
224527
|
// src/query_timeout.ts
|
|
224521
|
-
var import_api3 = __toESM(require_src(), 1);
|
|
224522
224528
|
init_errors();
|
|
224523
224529
|
var queryTimeoutCounter = null;
|
|
224524
224530
|
var timeoutTelemetryInitialized = false;
|
|
@@ -224526,7 +224532,7 @@ function ensureTimeoutTelemetry() {
|
|
|
224526
224532
|
if (queryTimeoutCounter && timeoutTelemetryInitialized) {
|
|
224527
224533
|
return queryTimeoutCounter;
|
|
224528
224534
|
}
|
|
224529
|
-
const meter2 =
|
|
224535
|
+
const meter2 = publisherMeter();
|
|
224530
224536
|
if (!queryTimeoutCounter) {
|
|
224531
224537
|
queryTimeoutCounter = meter2.createCounter("publisher_query_timeout_total", {
|
|
224532
224538
|
description: "Queries aborted because PUBLISHER_QUERY_TIMEOUT_MS elapsed before the underlying SDK call completed"
|
|
@@ -230137,7 +230143,10 @@ async function listTablesForSchema(connection, schemaName, malloyConnection, tab
|
|
|
230137
230143
|
async function listTablesForBigQuery(connection, schemaName, malloyConnection, tableNames) {
|
|
230138
230144
|
try {
|
|
230139
230145
|
const bigquery = createBigQueryClient(connection);
|
|
230140
|
-
const
|
|
230146
|
+
const lastDot = schemaName.lastIndexOf(".");
|
|
230147
|
+
const dataset = lastDot === -1 ? bigquery.dataset(schemaName) : bigquery.dataset(schemaName.slice(lastDot + 1), {
|
|
230148
|
+
projectId: schemaName.slice(0, lastDot)
|
|
230149
|
+
});
|
|
230141
230150
|
const [tables] = await dataset.getTables();
|
|
230142
230151
|
let names = tables.map((table) => table.id).filter((id) => id !== undefined);
|
|
230143
230152
|
if (tableNames) {
|
|
@@ -237687,7 +237696,6 @@ class StorageManager {
|
|
|
237687
237696
|
}
|
|
237688
237697
|
|
|
237689
237698
|
// src/service/environment.ts
|
|
237690
|
-
var import_api7 = __toESM(require_src(), 1);
|
|
237691
237699
|
import { MalloyError as MalloyError4, Runtime as Runtime2 } from "@malloydata/malloy";
|
|
237692
237700
|
init_constants();
|
|
237693
237701
|
init_errors();
|
|
@@ -237698,17 +237706,19 @@ import * as path8 from "path";
|
|
|
237698
237706
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
237699
237707
|
|
|
237700
237708
|
// src/materialization_metrics.ts
|
|
237701
|
-
var import_api4 = __toESM(require_src(), 1);
|
|
237702
237709
|
var resetHooks = [];
|
|
237703
237710
|
function lazyCounter(name, description) {
|
|
237704
237711
|
let instrument2 = null;
|
|
237705
237712
|
resetHooks.push(() => instrument2 = null);
|
|
237706
|
-
return () => instrument2 ??=
|
|
237713
|
+
return () => instrument2 ??= publisherMeter().createCounter(name, { description });
|
|
237707
237714
|
}
|
|
237708
237715
|
function lazyHistogram(name, description, unit) {
|
|
237709
237716
|
let instrument2 = null;
|
|
237710
237717
|
resetHooks.push(() => instrument2 = null);
|
|
237711
|
-
return () => instrument2 ??=
|
|
237718
|
+
return () => instrument2 ??= publisherMeter().createHistogram(name, {
|
|
237719
|
+
description,
|
|
237720
|
+
unit
|
|
237721
|
+
});
|
|
237712
237722
|
}
|
|
237713
237723
|
var runCounter = lazyCounter("publisher_materialization_runs_total", "Materialization builds completed. Labels: mode ('auto'|'orchestrated'), outcome ('success'|'failed'|'cancelled').");
|
|
237714
237724
|
var runDuration = lazyHistogram("publisher_materialization_run_duration_ms", "Wall-clock duration of a materialization build. Label: mode ('auto'|'orchestrated').", "ms");
|
|
@@ -237828,12 +237838,6 @@ async function fetchManifestEntries(uri) {
|
|
|
237828
237838
|
}
|
|
237829
237839
|
|
|
237830
237840
|
// src/service/package.ts
|
|
237831
|
-
init_package_load_pool();
|
|
237832
|
-
init_constants();
|
|
237833
|
-
init_errors();
|
|
237834
|
-
init_logger();
|
|
237835
|
-
var import_api6 = __toESM(require_src(), 1);
|
|
237836
|
-
var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
|
|
237837
237841
|
import * as fs6 from "fs/promises";
|
|
237838
237842
|
import * as path7 from "path";
|
|
237839
237843
|
import"@malloydata/db-duckdb/native";
|
|
@@ -237845,13 +237849,17 @@ import {
|
|
|
237845
237849
|
MalloyConfig as MalloyConfig3,
|
|
237846
237850
|
MalloyError as MalloyError3
|
|
237847
237851
|
} from "@malloydata/malloy";
|
|
237852
|
+
init_package_load_pool();
|
|
237853
|
+
init_constants();
|
|
237854
|
+
init_errors();
|
|
237855
|
+
init_logger();
|
|
237856
|
+
var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
|
|
237848
237857
|
|
|
237849
237858
|
// src/service/build_plan.ts
|
|
237850
237859
|
init_constants();
|
|
237851
237860
|
init_logger();
|
|
237852
237861
|
|
|
237853
237862
|
// src/service/model.ts
|
|
237854
|
-
var import_api5 = __toESM(require_src(), 1);
|
|
237855
237863
|
import {
|
|
237856
237864
|
Annotations as Annotations2,
|
|
237857
237865
|
API,
|
|
@@ -238288,6 +238296,24 @@ function assertWithinModelResponseLimits(rowCount, serializedBytes, { maxRows, m
|
|
|
238288
238296
|
}
|
|
238289
238297
|
}
|
|
238290
238298
|
|
|
238299
|
+
// src/service/query_text.ts
|
|
238300
|
+
function extractRunTargetSourceName(query) {
|
|
238301
|
+
if (!query)
|
|
238302
|
+
return;
|
|
238303
|
+
const runMatch = query.match(/run\s*:\s*(?:`([^`]+)`|(\w+))\s*->/);
|
|
238304
|
+
const arrowMatch = query.match(/^\s*(?:`([^`]+)`|(\w+))\s*->/m);
|
|
238305
|
+
return runMatch?.[1] ?? runMatch?.[2] ?? arrowMatch?.[1] ?? arrowMatch?.[2];
|
|
238306
|
+
}
|
|
238307
|
+
function buildSourceAliasMap(query) {
|
|
238308
|
+
const aliasOf = new Map;
|
|
238309
|
+
const declRe = /source\s*:\s*(?:`([^`]+)`|(\w+))\s+is\s+(?:`([^`]+)`|(\w+))/g;
|
|
238310
|
+
let match;
|
|
238311
|
+
while ((match = declRe.exec(query)) !== null) {
|
|
238312
|
+
aliasOf.set(match[1] ?? match[2], match[3] ?? match[4]);
|
|
238313
|
+
}
|
|
238314
|
+
return aliasOf;
|
|
238315
|
+
}
|
|
238316
|
+
|
|
238291
238317
|
// src/service/source_extraction.ts
|
|
238292
238318
|
import {
|
|
238293
238319
|
isSourceDef
|
|
@@ -238390,7 +238416,7 @@ class Model {
|
|
|
238390
238416
|
fileLevelAuthorize = [];
|
|
238391
238417
|
discoveryCurationEnabled = false;
|
|
238392
238418
|
queryBoundary = { mode: "all", exploresDeclared: false, isQueryEntryPoint: true };
|
|
238393
|
-
meter =
|
|
238419
|
+
meter = publisherMeter();
|
|
238394
238420
|
queryExecutionHistogram = this.meter.createHistogram("malloy_model_query_duration", {
|
|
238395
238421
|
description: "How long it takes to execute a Malloy model query",
|
|
238396
238422
|
unit: "ms"
|
|
@@ -238463,7 +238489,7 @@ class Model {
|
|
|
238463
238489
|
deny();
|
|
238464
238490
|
}
|
|
238465
238491
|
async assertAuthorizedForText(text, givens) {
|
|
238466
|
-
await this.assertAuthorized(
|
|
238492
|
+
await this.assertAuthorized(extractRunTargetSourceName(text), givens);
|
|
238467
238493
|
}
|
|
238468
238494
|
async assertAuthorizedForRunnable(runnable, givens) {
|
|
238469
238495
|
await this.assertAuthorized(await this.resolveAuthorizeSourceFromRunnable(runnable), givens);
|
|
@@ -238481,18 +238507,11 @@ class Model {
|
|
|
238481
238507
|
} catch {}
|
|
238482
238508
|
return;
|
|
238483
238509
|
}
|
|
238484
|
-
extractSourceName(query) {
|
|
238485
|
-
if (!query)
|
|
238486
|
-
return;
|
|
238487
|
-
const runMatch = query.match(/run\s*:\s*(?:`([^`]+)`|(\w+))\s*->/);
|
|
238488
|
-
const arrowMatch = query.match(/^\s*(?:`([^`]+)`|(\w+))\s*->/m);
|
|
238489
|
-
return runMatch?.[1] ?? runMatch?.[2] ?? arrowMatch?.[1] ?? arrowMatch?.[2];
|
|
238490
|
-
}
|
|
238491
238510
|
resolveFilterSource(query) {
|
|
238492
|
-
const target =
|
|
238511
|
+
const target = extractRunTargetSourceName(query);
|
|
238493
238512
|
if (!target || !query)
|
|
238494
238513
|
return;
|
|
238495
|
-
const aliasOf =
|
|
238514
|
+
const aliasOf = buildSourceAliasMap(query);
|
|
238496
238515
|
let current = target;
|
|
238497
238516
|
const seen = new Set;
|
|
238498
238517
|
while (current && !seen.has(current)) {
|
|
@@ -238655,7 +238674,7 @@ class Model {
|
|
|
238655
238674
|
throw new NotQueryableError(`No queryable source "${sourceName}".`);
|
|
238656
238675
|
}
|
|
238657
238676
|
if (query) {
|
|
238658
|
-
const target =
|
|
238677
|
+
const target = extractRunTargetSourceName(query);
|
|
238659
238678
|
if (target && !curatedSources.has(target) && !this.derivesFromCurated(target, query) && this.sources?.some((s) => s.name === target)) {
|
|
238660
238679
|
throw new NotQueryableError(`No queryable source "${target}".`);
|
|
238661
238680
|
}
|
|
@@ -238690,7 +238709,7 @@ class Model {
|
|
|
238690
238709
|
}
|
|
238691
238710
|
derivesFromCurated(name, query) {
|
|
238692
238711
|
const curated = this.curatedSourceNames();
|
|
238693
|
-
const aliasOf =
|
|
238712
|
+
const aliasOf = buildSourceAliasMap(query);
|
|
238694
238713
|
let current = name;
|
|
238695
238714
|
const seen = new Set;
|
|
238696
238715
|
while (current && !seen.has(current)) {
|
|
@@ -238701,15 +238720,6 @@ class Model {
|
|
|
238701
238720
|
}
|
|
238702
238721
|
return false;
|
|
238703
238722
|
}
|
|
238704
|
-
static buildAliasMap(query) {
|
|
238705
|
-
const aliasOf = new Map;
|
|
238706
|
-
const declRe = /source\s*:\s*(?:`([^`]+)`|(\w+))\s+is\s+(?:`([^`]+)`|(\w+))/g;
|
|
238707
|
-
let match;
|
|
238708
|
-
while ((match = declRe.exec(query)) !== null) {
|
|
238709
|
-
aliasOf.set(match[1] ?? match[2], match[3] ?? match[4]);
|
|
238710
|
-
}
|
|
238711
|
-
return aliasOf;
|
|
238712
|
-
}
|
|
238713
238723
|
async validateRenderTags() {
|
|
238714
238724
|
const mm = this.modelMaterializer;
|
|
238715
238725
|
if (!mm) {
|
|
@@ -238786,7 +238796,7 @@ class Model {
|
|
|
238786
238796
|
if (!this.modelMaterializer || !this.modelDef || !this.modelInfo)
|
|
238787
238797
|
throw new BadRequestError("Model has no queryable entities.");
|
|
238788
238798
|
const boundary = this.assertQueryBoundaryEarly(sourceName, queryName, query);
|
|
238789
|
-
const earlySource = sourceName || (queryName ? this.queries?.find((q) => q.name === queryName)?.sourceName : undefined) ||
|
|
238799
|
+
const earlySource = sourceName || (queryName ? this.queries?.find((q) => q.name === queryName)?.sourceName : undefined) || extractRunTargetSourceName(query);
|
|
238790
238800
|
if (earlySource) {
|
|
238791
238801
|
await this.assertAuthorized(earlySource, givens ?? {});
|
|
238792
238802
|
}
|
|
@@ -238971,7 +238981,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`;
|
|
|
238971
238981
|
try {
|
|
238972
238982
|
let runnableToExecute = cell.runnable;
|
|
238973
238983
|
if (!bypassFilters && cell.modelMaterializer) {
|
|
238974
|
-
const effectiveSource =
|
|
238984
|
+
const effectiveSource = extractRunTargetSourceName(cell.text);
|
|
238975
238985
|
if (effectiveSource) {
|
|
238976
238986
|
const filters = this.getFilters(effectiveSource);
|
|
238977
238987
|
if (filters.length > 0) {
|
|
@@ -239441,7 +239451,7 @@ class Package {
|
|
|
239441
239451
|
manifestEntryCount = 0;
|
|
239442
239452
|
boundManifestUri = null;
|
|
239443
239453
|
buildPlan = null;
|
|
239444
|
-
static meter =
|
|
239454
|
+
static meter = publisherMeter();
|
|
239445
239455
|
static packageLoadHistogram = this.meter.createHistogram("malloy_package_load_duration", {
|
|
239446
239456
|
description: "Time taken to load a Malloy package",
|
|
239447
239457
|
unit: "ms"
|
|
@@ -239555,7 +239565,8 @@ class Package {
|
|
|
239555
239565
|
resource: `${API_PREFIX}/environments/${environmentName}/packages/${packageName}`,
|
|
239556
239566
|
explores: outcome.packageMetadata.explores,
|
|
239557
239567
|
queryableSources: outcome.packageMetadata.queryableSources,
|
|
239558
|
-
manifestLocation: outcome.packageMetadata.manifestLocation ?? null
|
|
239568
|
+
manifestLocation: outcome.packageMetadata.manifestLocation ?? null,
|
|
239569
|
+
materialization: outcome.packageMetadata.materialization ?? null
|
|
239559
239570
|
};
|
|
239560
239571
|
const models = new Map;
|
|
239561
239572
|
for (const sm of outcome.models) {
|
|
@@ -239919,7 +239930,7 @@ var packageAdmissionRejectionsCounter = null;
|
|
|
239919
239930
|
function getQueryAdmissionRejectionsCounter() {
|
|
239920
239931
|
if (queryAdmissionRejectionsCounter)
|
|
239921
239932
|
return queryAdmissionRejectionsCounter;
|
|
239922
|
-
queryAdmissionRejectionsCounter =
|
|
239933
|
+
queryAdmissionRejectionsCounter = publisherMeter().createCounter("publisher_query_admission_rejections_total", {
|
|
239923
239934
|
description: "Queries rejected with 503 because Environment.assertCanAdmitQuery() observed memory back-pressure"
|
|
239924
239935
|
});
|
|
239925
239936
|
return queryAdmissionRejectionsCounter;
|
|
@@ -239928,7 +239939,7 @@ function getPackageAdmissionRejectionsCounter() {
|
|
|
239928
239939
|
if (packageAdmissionRejectionsCounter) {
|
|
239929
239940
|
return packageAdmissionRejectionsCounter;
|
|
239930
239941
|
}
|
|
239931
|
-
packageAdmissionRejectionsCounter =
|
|
239942
|
+
packageAdmissionRejectionsCounter = publisherMeter().createCounter("publisher_package_admission_rejections_total", {
|
|
239932
239943
|
description: "Package loads rejected with 503 because Environment.assertCanAdmitNewPackage() observed memory back-pressure"
|
|
239933
239944
|
});
|
|
239934
239945
|
return packageAdmissionRejectionsCounter;
|
|
@@ -241853,14 +241864,13 @@ var setFilterDeprecationHeaders = (res, options) => {
|
|
|
241853
241864
|
|
|
241854
241865
|
// src/heap_check.ts
|
|
241855
241866
|
init_logger();
|
|
241856
|
-
var import_api8 = __toESM(require_src(), 1);
|
|
241857
241867
|
import * as v8 from "v8";
|
|
241858
241868
|
var heapGaugesInstalled = false;
|
|
241859
241869
|
function installHeapGauges() {
|
|
241860
241870
|
if (heapGaugesInstalled)
|
|
241861
241871
|
return;
|
|
241862
241872
|
heapGaugesInstalled = true;
|
|
241863
|
-
const meter2 =
|
|
241873
|
+
const meter2 = publisherMeter();
|
|
241864
241874
|
meter2.createObservableGauge("publisher_heap_size_limit_bytes", {
|
|
241865
241875
|
description: "V8 heap_size_limit (--max-old-space-size). Compare with PUBLISHER_MAX_MEMORY_BYTES.",
|
|
241866
241876
|
unit: "By"
|
|
@@ -241895,7 +241905,6 @@ function checkHeapConfiguration(options = {}) {
|
|
|
241895
241905
|
}
|
|
241896
241906
|
|
|
241897
241907
|
// src/query_concurrency.ts
|
|
241898
|
-
var import_api9 = __toESM(require_src(), 1);
|
|
241899
241908
|
init_errors();
|
|
241900
241909
|
init_logger();
|
|
241901
241910
|
var active = 0;
|
|
@@ -241905,7 +241914,7 @@ function ensureConcurrencyTelemetry() {
|
|
|
241905
241914
|
if (queryConcurrencyRejectionsCounter && concurrencyTelemetryInitialized) {
|
|
241906
241915
|
return queryConcurrencyRejectionsCounter;
|
|
241907
241916
|
}
|
|
241908
|
-
const meter2 =
|
|
241917
|
+
const meter2 = publisherMeter();
|
|
241909
241918
|
queryConcurrencyRejectionsCounter = meter2.createCounter("publisher_query_concurrency_rejections_total", {
|
|
241910
241919
|
description: "Queries rejected with 503 because the per-pod PUBLISHER_MAX_CONCURRENT_QUERIES cap was reached"
|
|
241911
241920
|
});
|
|
@@ -249054,7 +249063,6 @@ class MaterializationService {
|
|
|
249054
249063
|
|
|
249055
249064
|
// src/service/package_memory_governor.ts
|
|
249056
249065
|
init_logger();
|
|
249057
|
-
var import_api10 = __toESM(require_src(), 1);
|
|
249058
249066
|
var DEFAULT_RSS_SAMPLER = () => process.memoryUsage().rss;
|
|
249059
249067
|
|
|
249060
249068
|
class PackageMemoryGovernor {
|
|
@@ -249072,7 +249080,7 @@ class PackageMemoryGovernor {
|
|
|
249072
249080
|
this.rssSampler = rssSampler ?? DEFAULT_RSS_SAMPLER;
|
|
249073
249081
|
this.highWaterBytes = Math.floor(config.maxMemoryBytes * config.highWaterFraction);
|
|
249074
249082
|
this.lowWaterBytes = Math.floor(config.maxMemoryBytes * config.lowWaterFraction);
|
|
249075
|
-
const meter2 =
|
|
249083
|
+
const meter2 = publisherMeter();
|
|
249076
249084
|
meter2.createObservableGauge("publisher_process_rss_bytes", {
|
|
249077
249085
|
description: "Current resident set size of the publisher process in bytes",
|
|
249078
249086
|
unit: "By"
|
package/package.json
CHANGED
|
@@ -195,9 +195,9 @@ export class PackageController {
|
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
197
|
* Run the right downloader for the given location into `targetPath`.
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
198
|
+
* Callers pass a sibling staging dir (not the canonical package
|
|
199
|
+
* directory) so the long-running download doesn't hold the per-package
|
|
200
|
+
* mutex.
|
|
201
201
|
*/
|
|
202
202
|
private async downloadInto(
|
|
203
203
|
environmentName: string,
|
package/src/heap_check.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { publisherMeter } from "./telemetry";
|
|
2
2
|
import * as v8 from "v8";
|
|
3
3
|
|
|
4
4
|
import { logger } from "./logger";
|
|
@@ -32,7 +32,7 @@ let heapGaugesInstalled = false;
|
|
|
32
32
|
function installHeapGauges(): void {
|
|
33
33
|
if (heapGaugesInstalled) return;
|
|
34
34
|
heapGaugesInstalled = true;
|
|
35
|
-
const meter =
|
|
35
|
+
const meter = publisherMeter();
|
|
36
36
|
meter
|
|
37
37
|
.createObservableGauge("publisher_heap_size_limit_bytes", {
|
|
38
38
|
description:
|
package/src/instrumentation.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
2
|
-
import { metrics } from "@opentelemetry/api";
|
|
3
2
|
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
4
3
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
5
4
|
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
} from "@opentelemetry/semantic-conventions";
|
|
19
18
|
import type { NextFunction, Request, Response } from "express";
|
|
20
19
|
import { logger } from "./logger";
|
|
20
|
+
import { publisherMeter } from "./telemetry";
|
|
21
21
|
|
|
22
22
|
let prometheusExporter: PrometheusExporter | null = null;
|
|
23
23
|
let sdk: NodeSDK | null = null;
|
|
@@ -98,7 +98,7 @@ instrument();
|
|
|
98
98
|
|
|
99
99
|
// --- HTTP metrics middleware ---
|
|
100
100
|
|
|
101
|
-
const meter =
|
|
101
|
+
const meter = publisherMeter();
|
|
102
102
|
|
|
103
103
|
const httpRequestDuration = meter.createHistogram(
|
|
104
104
|
"http_server_request_duration_ms",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* (https://github.com/open-telemetry/opentelemetry-js/issues/3505).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { type Counter, type Histogram } from "@opentelemetry/api";
|
|
16
|
+
import { publisherMeter } from "./telemetry";
|
|
16
17
|
|
|
17
18
|
export type MaterializationMode = "auto" | "orchestrated";
|
|
18
19
|
export type MaterializationOutcome = "success" | "failed" | "cancelled";
|
|
@@ -30,9 +31,7 @@ function lazyCounter(name: string, description: string): () => Counter {
|
|
|
30
31
|
let instrument: Counter | null = null;
|
|
31
32
|
resetHooks.push(() => (instrument = null));
|
|
32
33
|
return () =>
|
|
33
|
-
(instrument ??=
|
|
34
|
-
.getMeter("publisher")
|
|
35
|
-
.createCounter(name, { description }));
|
|
34
|
+
(instrument ??= publisherMeter().createCounter(name, { description }));
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function lazyHistogram(
|
|
@@ -43,9 +42,10 @@ function lazyHistogram(
|
|
|
43
42
|
let instrument: Histogram | null = null;
|
|
44
43
|
resetHooks.push(() => (instrument = null));
|
|
45
44
|
return () =>
|
|
46
|
-
(instrument ??=
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
(instrument ??= publisherMeter().createHistogram(name, {
|
|
46
|
+
description,
|
|
47
|
+
unit,
|
|
48
|
+
}));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const runCounter = lazyCounter(
|
|
@@ -54,8 +54,7 @@
|
|
|
54
54
|
* - **Job timeout.** `PACKAGE_LOAD_JOB_TIMEOUT_MS` (default 120s) caps a
|
|
55
55
|
* single package load. On timeout we **terminate the worker** and
|
|
56
56
|
* reject the caller — we do not silently fall back, and we do not
|
|
57
|
-
* leave a zombie compile burning CPU on the worker
|
|
58
|
-
* exact scenario PR-#767's job-timeout-without-terminate had).
|
|
57
|
+
* leave a zombie compile burning CPU on the worker.
|
|
59
58
|
*
|
|
60
59
|
* Schema-fetch RPC routing
|
|
61
60
|
* ------------------------
|
|
@@ -234,6 +233,7 @@ export interface LoadPackageOutcome {
|
|
|
234
233
|
explores?: string[];
|
|
235
234
|
queryableSources?: "declared" | "all";
|
|
236
235
|
manifestLocation?: string | null;
|
|
236
|
+
materialization?: { schedule: string | null } | null;
|
|
237
237
|
};
|
|
238
238
|
models: Array<
|
|
239
239
|
Omit<SerializedModel, "modelDef" | "sourceInfos"> & {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* duplicates the in-memory DB state and adds native-module load
|
|
17
17
|
* latency to every worker spawn. Database probing (`readDatabases`)
|
|
18
18
|
* stays on the main thread where it can reuse the package's existing
|
|
19
|
-
* DuckDB connection
|
|
19
|
+
* DuckDB connection.
|
|
20
20
|
*
|
|
21
21
|
* Boundary
|
|
22
22
|
* --------
|
|
@@ -83,6 +83,7 @@ import { HackyDataStylesAccumulator } from "../data_styles";
|
|
|
83
83
|
import { ModelCompilationError } from "../errors";
|
|
84
84
|
import { validateAuthorizeProbes } from "../service/authorize";
|
|
85
85
|
import { type FilterDefinition } from "../service/filter";
|
|
86
|
+
import { parsePackageMaterialization } from "../service/package_manifest";
|
|
86
87
|
import {
|
|
87
88
|
extractQueriesFromModelDef,
|
|
88
89
|
extractSourcesFromModelDef,
|
|
@@ -382,6 +383,7 @@ async function readPackageMetadata(packagePath: string): Promise<{
|
|
|
382
383
|
explores?: string[];
|
|
383
384
|
queryableSources?: "declared" | "all";
|
|
384
385
|
manifestLocation?: string | null;
|
|
386
|
+
materialization?: { schedule: string | null } | null;
|
|
385
387
|
}> {
|
|
386
388
|
const manifestPath = path.join(packagePath, PACKAGE_MANIFEST_NAME);
|
|
387
389
|
const contents = await fs.promises.readFile(manifestPath, "utf8");
|
|
@@ -391,6 +393,7 @@ async function readPackageMetadata(packagePath: string): Promise<{
|
|
|
391
393
|
explores?: string[];
|
|
392
394
|
queryableSources?: unknown;
|
|
393
395
|
manifestLocation?: unknown;
|
|
396
|
+
materialization?: unknown;
|
|
394
397
|
};
|
|
395
398
|
return {
|
|
396
399
|
name: parsed.name,
|
|
@@ -407,6 +410,9 @@ async function readPackageMetadata(packagePath: string): Promise<{
|
|
|
407
410
|
typeof parsed.manifestLocation === "string"
|
|
408
411
|
? parsed.manifestLocation
|
|
409
412
|
: null,
|
|
413
|
+
// Package-level Malloy Persistence policy; surfaced to the control plane,
|
|
414
|
+
// which owns scheduling. Only `schedule` is read today.
|
|
415
|
+
materialization: parsePackageMaterialization(parsed.materialization),
|
|
410
416
|
};
|
|
411
417
|
}
|
|
412
418
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Embedded database probing (`.parquet` / `.csv` schema + row count)
|
|
19
19
|
* stays on the main thread — it reuses the package's existing DuckDB
|
|
20
|
-
* connection
|
|
20
|
+
* connection and the probe queries are async-IO-bound, not
|
|
21
21
|
* CPU-bound. Keeping all native-DB handles on the main thread also
|
|
22
22
|
* sidesteps Bun crash 0x20131 where duckdb-native cannot be loaded
|
|
23
23
|
* into more than one isolate of the same process.
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* (live native handles can't cross the worker boundary).
|
|
28
28
|
* - Lazy-hydrating each model's `ModelMaterializer` from `modelDef`
|
|
29
29
|
* via `Runtime._loadModelFromModelDef` on first query — NO
|
|
30
|
-
* recompile
|
|
31
|
-
*
|
|
30
|
+
* recompile, so the first query never re-runs the compiler on
|
|
31
|
+
* the main thread.
|
|
32
32
|
*
|
|
33
33
|
* Per-model compile failures are returned in-band on
|
|
34
34
|
* `SerializedModel.compilationError` so a single bad model doesn't
|
|
@@ -179,6 +179,7 @@ export interface LoadPackageResult {
|
|
|
179
179
|
explores?: string[];
|
|
180
180
|
queryableSources?: "declared" | "all";
|
|
181
181
|
manifestLocation?: string | null;
|
|
182
|
+
materialization?: { schedule: string | null } | null;
|
|
182
183
|
};
|
|
183
184
|
models: SerializedModel[];
|
|
184
185
|
/** Wall-clock ms inside the worker for the full package load. */
|
package/src/query_cap_metrics.ts
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* is one boolean check after the first 413.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import {
|
|
28
|
+
import { type Counter } from "@opentelemetry/api";
|
|
29
|
+
import { publisherMeter } from "./telemetry";
|
|
29
30
|
|
|
30
31
|
import { getMaxQueryRows, getMaxResponseBytes } from "./config";
|
|
31
32
|
|
|
@@ -39,7 +40,7 @@ function ensureCapTelemetry(): Counter {
|
|
|
39
40
|
if (capExceededCounter && configGaugesInstalled) {
|
|
40
41
|
return capExceededCounter;
|
|
41
42
|
}
|
|
42
|
-
const meter =
|
|
43
|
+
const meter = publisherMeter();
|
|
43
44
|
if (!capExceededCounter) {
|
|
44
45
|
capExceededCounter = meter.createCounter(
|
|
45
46
|
"publisher_query_cap_exceeded_total",
|
package/src/query_concurrency.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Counter } from "@opentelemetry/api";
|
|
2
|
+
import { publisherMeter } from "./telemetry";
|
|
2
3
|
import type { NextFunction, Request, RequestHandler, Response } from "express";
|
|
3
4
|
|
|
4
5
|
import { getMaxConcurrentQueries } from "./config";
|
|
@@ -30,7 +31,7 @@ function ensureConcurrencyTelemetry(): Counter {
|
|
|
30
31
|
if (queryConcurrencyRejectionsCounter && concurrencyTelemetryInitialized) {
|
|
31
32
|
return queryConcurrencyRejectionsCounter;
|
|
32
33
|
}
|
|
33
|
-
const meter =
|
|
34
|
+
const meter = publisherMeter();
|
|
34
35
|
queryConcurrencyRejectionsCounter = meter.createCounter(
|
|
35
36
|
"publisher_query_concurrency_rejections_total",
|
|
36
37
|
{
|