Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split metrics from device resource #1371

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

fisehara
Copy link
Contributor

@fisehara fisehara commented Jul 4, 2023

Remodel device resource and split metrics into device metrics record

This reduces the load on the device resource when updating device metrics periodically. device metrics are far less frequently needed than the device resource. Thus writing to the heavily used device resource should be offloaded to the device metrics record resource.

Change-type: major

@fisehara fisehara marked this pull request as draft July 4, 2023 09:05
@fisehara fisehara force-pushed the fisehara/split-device-metrics branch 2 times, most recently from 65c5775 to 70730be Compare July 4, 2023 12:53
@fisehara fisehara requested review from thgreasi and Page- July 4, 2023 12:57
@fisehara fisehara force-pushed the fisehara/split-device-metrics branch from 70730be to 4b2e937 Compare July 4, 2023 13:26
Comment on lines 215 to 217
console.log(
`patch metricsBody:${JSON.stringify(metricsBody, null, 2)}`,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need removing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

Comment on lines 330 to 333
const latestDeviceMetricsRecord = await resinApiTx.get({
resource: 'device_metrics_record',
id: { is_reported_by__device: device.id },
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be merged into the fetchData request rather than adding an extra one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, great hint also explicitly loading with readOnly transcation.

I've added it to the device request by expanding the reports__device_metrics_record

Comment on lines +18 to +19
CREATE INDEX IF NOT EXISTS "device_metrics_record_by_device_idx"
ON "device metrics record" ("is reported by-device");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be included in balena-init.sql

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

Copy link
Member

@thgreasi thgreasi Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Page- @fisehara Afaict we shouldn't need to add a manual index (neither here nor in the balena-init.sql), since the "is reported by-device" INTEGER NOT NULL UNIQUE part of the CREATE TABLE does auto-generate the following index:

CREATE UNIQUE INDEX "device metrics record_is reported by-device_key" ON public."device metrics record" USING ("is reported by-device");

which makes device_metrics_record_by_device_idx redundant/duplicate.

If we still prefer to explicitly be adding index as part of the migration just in case, then it should probably be matching the auto-generated one. (but tbh I don't think we should b/c pg adds it out of the box)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a unique constraint will automatically create a unique btree index on the column or group of columns used in the constraint.

See: https://www.postgresql.org/docs/current/sql-createtable.html

src/balena.sbvr Outdated
Comment on lines 531 to 545
Fact type: device has memory usage
Necessity: each device has at most one memory usage
Fact type: device has memory total
Necessity: each device has at most one memory total
Fact type: device has storage block device
Necessity: each device has at most one storage block device
Fact type: device has storage usage
Necessity: each device has at most one storage usage
Fact type: device has storage total
Necessity: each device has at most one storage total
Fact type: device has cpu usage
Necessity: each device has at most one cpu usage
Fact type: device has cpu temp
Necessity: each device has at most one cpu temp
Fact type: device is undervolted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this can be safely removed until the async migration relying on it is finalized

@fisehara fisehara requested review from Page- and thgreasi and removed request for thgreasi July 6, 2023 22:04
Remodel `device` resource and split metrics into `device metrics record`

This reduces the load on the `device` resource when updating device metrics periodically.
device metrics are far less frequently needed than the `device` resource. Thus writing to the heavily used `device` resource should be offloaded to the `device metrics record` resource.

Change-type: major
Signed-off-by: Harald Fischer <harald@balena.io>
@fisehara fisehara force-pushed the fisehara/split-device-metrics branch from 4b2e937 to 533ab03 Compare July 6, 2023 22:05
@fisehara
Copy link
Contributor Author

fisehara commented Jul 6, 2023

@Page- @thgreasi when this is fine I'll follow up with a PR for finalising the async migration and dropping the columns from device resource.

await resinApiTx.patch({
resource: 'device_metrics_record',
id: { is_reported_by__device: device.id },
body: metricsBody,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding v both here and in the v2 patch?

Suggested change
body: metricsBody,
options: {
$filter: {
$not: metricsBody
}
},
body: metricsBody,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this avoids an unnecessary postgres write in the case there are no changes

@@ -23,9 +23,11 @@ export {
serviceInstallFromImage,
} from './state-get-utils';
export {
metricsPatchFields,
validDeviceMetricsRecordPatchFields,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Since this field is also exported from index.ts w/ export * as deviceState from './features/device-state';, how about not renaming it so that we don't introduce a breaking change?

@@ -188,3 +188,6 @@ ON "application" ("slug" varchar_pattern_ops, "is public", "is host");

CREATE INDEX IF NOT EXISTS "scheduled_job_run_start_timestamp_idx"
ON "scheduled job run" (DATE_TRUNC('milliseconds', "start timestamp"));

CREATE INDEX IF NOT EXISTS "device_metrics_record_by_device_idx"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder about the discussion on this at
See: https://github.com/balena-io/open-balena-api/pull/1371/files#r1255500040

FROM
"device metrics record"
)
FOR UPDATE SKIP LOCKED
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FOR UPDATE SKIP LOCKED
FOR UPDATE

The sync migration one should not leave anything pending.

Comment on lines +101 to +107
reports__device_metrics_record: {},
},
},
})) as Array<
Pick<Device, 'id' | 'uuid'> & {
belongs_to__application: Array<Pick<Application, 'uuid'>>;
reports__device_metrics_record: DeviceMetricsRecord[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really care to fetch any of the rest columns.

Suggested change
reports__device_metrics_record: {},
},
},
})) as Array<
Pick<Device, 'id' | 'uuid'> & {
belongs_to__application: Array<Pick<Application, 'uuid'>>;
reports__device_metrics_record: DeviceMetricsRecord[];
reports__device_metrics_record: {
$select: 'id'
},
},
},
})) as Array<
Pick<Device, 'id' | 'uuid'> & {
belongs_to__application: Array<Pick<Application, 'uuid'>>;
reports__device_metrics_record: reports__device_metrics_record: [Pick<DeviceMetricsRecord, 'id'>?];

Comment on lines +202 to +205
const latestDeviceMetricsRecord = await resinApiTx.get({
resource: 'device_metrics_record',
id: { is_reported_by__device: deviceId },
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const latestDeviceMetricsRecord = await resinApiTx.get({
resource: 'device_metrics_record',
id: { is_reported_by__device: deviceId },
});
const latestDeviceMetricsRecord = await resinApiTx.get({
resource: 'device_metrics_record',
id: { is_reported_by__device: deviceId },
options: {
$select: 'id'
}
});

Comment on lines +207 to +213
await resinApiTx.post({
resource: 'device_metrics_record',
id: { is_reported_by__device: deviceId },
body: {
...metricsBody,
},
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that work? Shouldn't it be v ?

Suggested change
await resinApiTx.post({
resource: 'device_metrics_record',
id: { is_reported_by__device: deviceId },
body: {
...metricsBody,
},
});
await resinApiTx.post({
resource: 'device_metrics_record',
body: {
...metricsBody,
is_reported_by__device: deviceId,
},
});

resource: 'device_metrics_record',
body: {
...metricsBody,
...{ is_reported_by__device: device.id },
Copy link
Member

@thgreasi thgreasi Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
...{ is_reported_by__device: device.id },
is_reported_by__device: device.id,

I less object to GC :)

} else {
await resinApiTx.patch({
resource: 'device_metrics_record',
id: { is_reported_by__device: deviceId },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
id: { is_reported_by__device: deviceId },
id: { is_reported_by__device: deviceId },
options: {
$filter: {
$not: metricsBody
}
},

if (apps != null || Object.keys(deviceBody).length > 0) {
if (
Object.keys(metricsBody).length > 0 &&
!(await shouldUpdateMetrics(uuid))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a special case I think where we always want to recognize changes to the is_undervolted property which we currently always accept but this PR has moved to being treated like any of the other "metrics" fields

await resinApiTx.patch({
resource: 'device_metrics_record',
id: { is_reported_by__device: device.id },
body: metricsBody,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this avoids an unnecessary postgres write in the case there are no changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants