-
Notifications
You must be signed in to change notification settings - Fork 30
feat/labs migration #1968
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
base: main
Are you sure you want to change the base?
feat/labs migration #1968
Changes from all commits
89129f9
2f222d7
22e3d04
cadc814
286950c
e2c753f
4c9cf04
f683ae8
7a29008
b4c96b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,6 +100,9 @@ def _standardize_lab_field(item: dict[str, Any], field: str) -> None: | |
| """ | ||
| lab = item.get("misc", {}).get(field) | ||
| is_automatic = lab and AUTOMATIC_LABS.match(lab) | ||
| # Real lab for the lab_id FK, captured before the origin fallback (#1752) below | ||
| # overwrites misc, which would otherwise pollute the lab dimension. | ||
| item["_real_lab"] = None if is_automatic else lab | ||
| if is_automatic: | ||
| item["misc"][AUTOMATIC_LAB_FIELD] = lab | ||
| item["misc"].pop(field, None) | ||
|
|
@@ -188,6 +191,39 @@ def prepare_file_data( | |
| } | ||
|
|
||
|
|
||
| def assign_lab_ids(builds_buf: list[Builds], tests_buf: list[Tests]) -> None: | ||
| """Resolve each instance's real lab name to a labs.id and set its lab_id FK. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the lab name is unique in the database, why not use that as a primary key? In that case you'd make a first query to check which labs are not in the database yet, a second query to update the table if needed, but you wouldn't need the third query to get the id of the newly added labs
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly to avoid string keys, since this is an internal key, we might benefit more from integer indices.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. I am worried that we are adding one query here, one query there and soon we will have a bloat of unnecessary queries in the ingester, but it's fine for now
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a valid concern. If we plain to move some operations to ingestion time, we certainly are going to end up increasing "ingestion complexity". But I believe that, as long as we keep the ingester fast enough, simplifying the analysis step should be the goal. |
||
|
|
||
| Select-first so we only INSERT genuinely new labs (avoids burning the id | ||
| sequence on every flush). New labs are committed outside the fact-insert | ||
| transaction (autocommit). | ||
| """ | ||
| objs = [*builds_buf, *tests_buf] | ||
| names = {name for obj in objs if (name := obj._lab_name)} | ||
|
|
||
| id_map: dict[str, int] = {} | ||
| if names: | ||
| with connections["default"].cursor() as cursor: | ||
| cursor.execute( | ||
| "SELECT id, name FROM labs WHERE name = ANY(%s)", [list(names)] | ||
| ) | ||
| id_map = {name: lab_id for lab_id, name in cursor.fetchall()} | ||
|
|
||
| missing = [name for name in names if name not in id_map] | ||
| if missing: | ||
| cursor.executemany( | ||
| "INSERT INTO labs (name) VALUES (%s) ON CONFLICT (name) DO NOTHING", | ||
| [(name,) for name in missing], | ||
| ) | ||
| cursor.execute( | ||
| "SELECT id, name FROM labs WHERE name = ANY(%s)", [missing] | ||
| ) | ||
| id_map.update({name: lab_id for lab_id, name in cursor.fetchall()}) | ||
|
|
||
| for obj in objs: | ||
| obj.lab_id = id_map.get(obj._lab_name) if obj._lab_name else None | ||
|
|
||
|
|
||
| def consume_buffer(buffer: list[TableModels], table_name: TableNames) -> None: | ||
| """ | ||
| Consume a buffer of items and insert them into the database. | ||
|
|
@@ -245,6 +281,8 @@ def flush_buffers( | |
| if total == 0: | ||
| return | ||
|
|
||
| assign_lab_ids(builds_buf, tests_buf) | ||
|
|
||
| # Insert in dependency-safe order | ||
| flush_start = time.time() | ||
| try: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Generated by Django 5.2.11 on 2026-06-29 20:25 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("kernelCI_app", "0018_hardwareregistryplatformvendor_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="Labs", | ||
| fields=[ | ||
| ("id", models.AutoField(primary_key=True, serialize=False)), | ||
| ("name", models.TextField(unique=True)), | ||
| ], | ||
| options={ | ||
| "db_table": "labs", | ||
| }, | ||
| ), | ||
| migrations.AddField( | ||
| model_name="builds", | ||
| name="lab", | ||
| field=models.ForeignKey( | ||
| null=True, | ||
| on_delete=django.db.models.deletion.DO_NOTHING, | ||
| to="kernelCI_app.labs", | ||
| ), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="tests", | ||
| name="lab", | ||
| field=models.ForeignKey( | ||
| null=True, | ||
| on_delete=django.db.models.deletion.DO_NOTHING, | ||
| to="kernelCI_app.labs", | ||
| ), | ||
| ), | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here the code we talked about. Not sure if this conditional should be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is tricky.
Post-migration we no longer read build_misc, so we can't distinguish "no misc at all" from "misc present but no lab" — we only have the lab field. And it's odd to branch on how lab is missing.
I think we should standardize: any missing lab as UNKNOWN.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do find a reason to treat them differently, we should include an explicit rule on this.