Repository: ALU-Schumacher/AUDITOR (language: rust)

Issue:
Auditor: Performance improvements for querying records
We noticed that the performance of querying records degrades when a lot of records (e.g. 100k or 1M) are stored in the DB. Even when we only query one record, this can take up to 40s. We should look into optimizing our SQL queries and try to reduce the query time.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/htcondor-collector.yaml b/.github/workflows/htcondor-collector.yaml
index f69efe1e5..b859618d6 100644
--- a/.github/workflows/htcondor-collector.yaml
+++ b/.github/workflows/htcondor-collector.yaml
@@ -86,12 +86,13 @@ jobs:
       run: |
         TABLE=$(docker exec htcondor-postgres-1 \
           bash -c "psql -U postgres -d auditor -c \" \
-            SELECT ac.runtime, sc.name, sc.value FROM accounting as ac \
-            JOIN records_components as rc ON ac.id=rc.record_id \
-            JOIN components AS co ON rc.component_id=co.id \
-            JOIN components_scores AS cs ON co.id=cs.component_id \
-            JOIN scores AS sc ON cs.score_id=sc.id \
-            ORDER BY sc.name; \
+            SELECT runtime, \
+            (score->>'name') AS name, \
+            (score->>'value')::double precision AS value \
+            FROM auditor, \
+            jsonb_array_elements(components) AS comp, \
+            jsonb_array_elements(comp->'scores') AS score \
+            ORDER BY name; \
           \"")
         echo "table<<EOF" >> $GITHUB_OUTPUT
         echo "$TABLE" >> $GITHUB_OUTPUT
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90279f0eb..5dda29f89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 
 ### Breaking changes
+- AUDITOR: Run `sqlx migrate run` to migrate to new schema for AUDITOR accounting db. ([@raghuvar-vijay](https://github.com/raghuvar-vijay))
 - Apel plugin: Use class structure for config and messages ([@dirksammel](https://github.com/dirksammel))
 - Apel plugin: Change `month` and `year` parameters of republish script to `begin_date` and `end_date` ([@dirksammel](https://github.com/dirksammel))
 - Apel plugin: Logging of APEL messages moved from DEBUG to TRACE level ([@dirksammel](https://github.com/dirksammel))
@@ -14,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Security
 
 ### Added
+- AUDITOR: Migrate to jsonb type for meta and component fields for AUDITOR postgresql. (#736) ([@raghuvar-vijay](https://github.com/raghuvar-vijay))
 - Client: Added a `QueuedAuditorClient` that queues records to be sent in a local SQLite database ([@rkleinem](https://github.com/rkleinem))
 - Apel plugin: Add option to send individual job messages ([@dirksammel](https://github.com/dirksammel))
 - CI: Add mypy workflow for type checking ([@dirksammel](https://github.com/dirksammel))
diff --git a/auditor/.sqlx/query-17f4686d8430a057a076f44db1d54859c3fb4f569d162d7095f8734b87ab73b7.json b/auditor/.sqlx/query-17f4686d8430a057a076f44db1d54859c3fb4f569d162d7095f8734b87ab73b7.json
deleted file mode 100644
index 3e6724972..000000000
--- a/auditor/.sqlx/query-17f4686d8430a057a076f44db1d54859c3fb4f569d162d7095f8734b87ab73b7.json
+++ /dev/null
@@ -1,100 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "SELECT a.record_id,\n                  m.meta as \"meta: Vec<(String, Vec<String>)>\",\n                  css.components as \"components: Vec<Component>\",\n                  a.start_time as \"start_time?\",\n                  a.stop_time,\n                  a.runtime\n           FROM accounting a\n           LEFT JOIN (\n               WITH subquery AS (\n                   SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values\n                   FROM meta as m\n                   GROUP BY m.record_id, m.key\n               )\n               SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta\n               FROM subquery as s\n               GROUP BY s.record_id\n               ) m ON m.record_id = a.record_id\n           LEFT JOIN (\n               WITH subquery AS (\n                  SELECT \n                      c.id as cid,\n                      COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores\n                  FROM components as c\n                  LEFT JOIN components_scores as cs\n                  ON c.id = cs.component_id\n                  LEFT JOIN scores as s\n                  ON cs.score_id = s.id\n                  GROUP BY c.id\n               )\n               SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components\n               FROM records_components AS rc\n               LEFT JOIN components as c\n               ON rc.component_id = c.id\n               LEFT JOIN subquery AS sq\n               ON sq.cid = rc.component_id\n               GROUP BY rc.record_id\n           ) css ON css.id = a.id\n           ORDER BY a.stop_time\n        ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "record_id",
-        "type_info": "Text"
-      },
-      {
-        "ordinal": 1,
-        "name": "meta: Vec<(String, Vec<String>)>",
-        "type_info": "RecordArray"
-      },
-      {
-        "ordinal": 2,
-        "name": "components: Vec<Component>",
-        "type_info": {
-          "Custom": {
-            "name": "_component",
-            "kind": {
-              "Array": {
-                "Custom": {
-                  "name": "component",
-                  "kind": {
-                    "Composite": [
-                      [
-                        "name",
-                        "Text"
-                      ],
-                      [
-                        "amount",
-                        "Int8"
-                      ],
-                      [
-                        "scores",
-                        {
-                          "Custom": {
-                            "name": "_score",
-                            "kind": {
-                              "Array": {
-                                "Custom": {
-                                  "name": "score",
-                                  "kind": {
-                                    "Composite": [
-                                      [
-                                        "name",
-                                        "Text"
-                                      ],
-                                      [
-                                        "value",
-                                        "Float8"
-                                      ]
-                                    ]
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        }
-                      ]
-                    ]
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      {
-        "ordinal": 3,
-        "name": "start_time?",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 4,
-        "name": "stop_time",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 5,
-        "name": "runtime",
-        "type_info": "Int8"
-      }
-    ],
-    "parameters": {
-      "Left": []
-    },
-    "nullable": [
-      false,
-      null,
-      null,
-      false,
-      true,
-      true
-    ]
-  },
-  "hash": "17f4686d8430a057a076f44db1d54859c3fb4f569d162d7095f8734b87ab73b7"
-}
diff --git a/auditor/.sqlx/query-262acf5fc2b4f747cd2e05ad2707e28f4d17fc26371012aeaece84cdf012c733.json b/auditor/.sqlx/query-262acf5fc2b4f747cd2e05ad2707e28f4d17fc26371012aeaece84cdf012c733.json
new file mode 100644
index 000000000..d817d43ff
--- /dev/null
+++ b/auditor/.sqlx/query-262acf5fc2b4f747cd2e05ad2707e28f4d17fc26371012aeaece84cdf012c733.json
@@ -0,0 +1,26 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "\n            SELECT jsonb_array_elements_text(meta->'site_id') AS \"name!\", COUNT(*) AS \"num!\"\n            FROM auditor\n            GROUP BY jsonb_array_elements_text(meta->'site_id');\n            ",
+  "describe": {
+    "columns": [
+      {
+        "ordinal": 0,
+        "name": "name!",
+        "type_info": "Text"
+      },
+      {
+        "ordinal": 1,
+        "name": "num!",
+        "type_info": "Int8"
+      }
+    ],
+    "parameters": {
+      "Left": []
+    },
+    "nullable": [
+      null,
+      null
+    ]
+  },
+  "hash": "262acf5fc2b4f747cd2e05ad2707e28f4d17fc26371012aeaece84cdf012c733"
+}
diff --git a/auditor/.sqlx/query-b205b9e460541ad09dcd6de4d75df490a88f4b31b3ef45a9ec68047c39081eb5.json b/auditor/.sqlx/query-2fd7111ca6a9819d4bfffa0280413bf144dc11f6d890a9558dd07dc6c92b45de.json
similarity index 64%
rename from auditor/.sqlx/query-b205b9e460541ad09dcd6de4d75df490a88f4b31b3ef45a9ec68047c39081eb5.json
rename to auditor/.sqlx/query-2fd7111ca6a9819d4bfffa0280413bf144dc11f6d890a9558dd07dc6c92b45de.json
index ef06218d4..2e8b0bd02 100644
--- a/auditor/.sqlx/query-b205b9e460541ad09dcd6de4d75df490a88f4b31b3ef45a9ec68047c39081eb5.json
+++ b/auditor/.sqlx/query-2fd7111ca6a9819d4bfffa0280413bf144dc11f6d890a9558dd07dc6c92b45de.json
@@ -1,6 +1,6 @@
 {
   "db_name": "PostgreSQL",
-  "query": "SELECT count(*) as \"count!\" FROM accounting;",
+  "query": "SELECT count(*) as \"count!\" FROM auditor;",
   "describe": {
     "columns": [
       {
@@ -16,5 +16,5 @@
       null
     ]
   },
-  "hash": "b205b9e460541ad09dcd6de4d75df490a88f4b31b3ef45a9ec68047c39081eb5"
+  "hash": "2fd7111ca6a9819d4bfffa0280413bf144dc11f6d890a9558dd07dc6c92b45de"
 }
diff --git a/auditor/.sqlx/query-3da9ad5248c244c040b47e27cbbca081b156fd5b88bffac65be6bb7ba7aabafe.json b/auditor/.sqlx/query-3da9ad5248c244c040b47e27cbbca081b156fd5b88bffac65be6bb7ba7aabafe.json
deleted file mode 100644
index d243e8549..000000000
--- a/auditor/.sqlx/query-3da9ad5248c244c040b47e27cbbca081b156fd5b88bffac65be6bb7ba7aabafe.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "\n                INSERT INTO meta (record_id, key, value)\n                SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])\n                ",
-  "describe": {
-    "columns": [],
-    "parameters": {
-      "Left": [
-        "TextArray",
-        "TextArray",
-        "TextArray"
-      ]
-    },
-    "nullable": []
-  },
-  "hash": "3da9ad5248c244c040b47e27cbbca081b156fd5b88bffac65be6bb7ba7aabafe"
-}
diff --git a/auditor/.sqlx/query-4e72ae54be447c525721306d208b926c647e3f8149bbd428a97ecbc9f8bceca5.json b/auditor/.sqlx/query-4e72ae54be447c525721306d208b926c647e3f8149bbd428a97ecbc9f8bceca5.json
deleted file mode 100644
index 9c488ee85..000000000
--- a/auditor/.sqlx/query-4e72ae54be447c525721306d208b926c647e3f8149bbd428a97ecbc9f8bceca5.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "SELECT a.record_id,\n                          m.meta as \"meta: Vec<(String, Vec<String>)>\",\n                          css.components as \"components: Vec<Component>\",\n                          a.start_time as \"start_time?\",\n                          a.stop_time,\n                          a.runtime\n                   FROM accounting a\n                   LEFT JOIN (\n                       WITH subquery AS (\n                           SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values\n                           FROM meta as m\n                           GROUP BY m.record_id, m.key\n                       )\n                       SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta\n                       FROM subquery as s\n                       GROUP BY s.record_id\n                       ) m ON m.record_id = a.record_id\n                   LEFT JOIN (\n                       WITH subquery AS (\n                          SELECT \n                              c.id as cid,\n                              COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores\n                          FROM components as c\n                          LEFT JOIN components_scores as cs\n                          ON c.id = cs.component_id\n                          LEFT JOIN scores as s\n                          ON cs.score_id = s.id\n                          GROUP BY c.id\n                       )\n                       SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components\n                       FROM records_components AS rc\n                       LEFT JOIN components as c\n                       ON rc.component_id = c.id\n                       LEFT JOIN subquery AS sq\n                       ON sq.cid = rc.component_id\n                       GROUP BY rc.record_id\n                   ) css ON css.id = a.id\n                WHERE a.stop_time > $1 and a.runtime IS NOT NULL\n                ORDER BY a.stop_time\n                ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "record_id",
-        "type_info": "Text"
-      },
-      {
-        "ordinal": 1,
-        "name": "meta: Vec<(String, Vec<String>)>",
-        "type_info": "RecordArray"
-      },
-      {
-        "ordinal": 2,
-        "name": "components: Vec<Component>",
-        "type_info": {
-          "Custom": {
-            "name": "_component",
-            "kind": {
-              "Array": {
-                "Custom": {
-                  "name": "component",
-                  "kind": {
-                    "Composite": [
-                      [
-                        "name",
-                        "Text"
-                      ],
-                      [
-                        "amount",
-                        "Int8"
-                      ],
-                      [
-                        "scores",
-                        {
-                          "Custom": {
-                            "name": "_score",
-                            "kind": {
-                              "Array": {
-                                "Custom": {
-                                  "name": "score",
-                                  "kind": {
-                                    "Composite": [
-                                      [
-                                        "name",
-                                        "Text"
-                                      ],
-                                      [
-                                        "value",
-                                        "Float8"
-                                      ]
-                                    ]
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        }
-                      ]
-                    ]
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      {
-        "ordinal": 3,
-        "name": "start_time?",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 4,
-        "name": "stop_time",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 5,
-        "name": "runtime",
-        "type_info": "Int8"
-      }
-    ],
-    "parameters": {
-      "Left": [
-        "Timestamptz"
-      ]
-    },
-    "nullable": [
-      false,
-      null,
-      null,
-      false,
-      true,
-      true
-    ]
-  },
-  "hash": "4e72ae54be447c525721306d208b926c647e3f8149bbd428a97ecbc9f8bceca5"
-}
diff --git a/auditor/.sqlx/query-36fad28130fa066e2182edb3019f5d7e77a635c6116aa9c8a5b358114fb8c57c.json b/auditor/.sqlx/query-4f6f0c4610070485bff505ce1207a4b77e206ab1558a7540e23a64165b95638e.json
similarity index 51%
rename from auditor/.sqlx/query-36fad28130fa066e2182edb3019f5d7e77a635c6116aa9c8a5b358114fb8c57c.json
rename to auditor/.sqlx/query-4f6f0c4610070485bff505ce1207a4b77e206ab1558a7540e23a64165b95638e.json
index 10a5a0601..0a136970d 100644
--- a/auditor/.sqlx/query-36fad28130fa066e2182edb3019f5d7e77a635c6116aa9c8a5b358114fb8c57c.json
+++ b/auditor/.sqlx/query-4f6f0c4610070485bff505ce1207a4b77e206ab1558a7540e23a64165b95638e.json
@@ -1,6 +1,6 @@
 {
   "db_name": "PostgreSQL",
-  "query": "\n        INSERT INTO accounting (\n            record_id, start_time, stop_time, runtime, updated_at\n        )\n        VALUES ($1, $2, $3, $4, $5)\n        RETURNING id;\n        ",
+  "query": "\n        INSERT INTO auditor (\n            record_id, start_time, stop_time, meta, components, runtime, updated_at\n        )\n        VALUES ($1, $2, $3, $4, $5, $6, $7)\n        RETURNING id;\n        ",
   "describe": {
     "columns": [
       {
@@ -14,6 +14,8 @@
         "Text",
         "Timestamptz",
         "Timestamptz",
+        "Jsonb",
+        "Jsonb",
         "Int8",
         "Timestamptz"
       ]
@@ -22,5 +24,5 @@
       false
     ]
   },
-  "hash": "36fad28130fa066e2182edb3019f5d7e77a635c6116aa9c8a5b358114fb8c57c"
+  "hash": "4f6f0c4610070485bff505ce1207a4b77e206ab1558a7540e23a64165b95638e"
 }
diff --git a/auditor/.sqlx/query-50c6776ca37b9b56b92848573f32d72f44631c9ff702bb468dc7a7f5520a6f48.json b/auditor/.sqlx/query-50c6776ca37b9b56b92848573f32d72f44631c9ff702bb468dc7a7f5520a6f48.json
deleted file mode 100644
index b41bb4c82..000000000
--- a/auditor/.sqlx/query-50c6776ca37b9b56b92848573f32d72f44631c9ff702bb468dc7a7f5520a6f48.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "\n                WITH insert_components AS (\n                    INSERT INTO components (name, amount)\n                    VALUES ($1, $2)\n                    RETURNING id\n                ),\n                insert_scores AS (\n                    INSERT INTO scores (name, value)\n                    SELECT * FROM UNNEST($3::text[], $4::double precision[])\n                    -- Update if already in table. This isn't great, but \n                    -- otherwise RETURNING won't return anything.\n                    ON CONFLICT (name, value) DO UPDATE\n                    SET value = EXCLUDED.value, name = EXCLUDED.name\n                    RETURNING id\n                ),\n                insert_components_scores AS (\n                    INSERT INTO components_scores (component_id, score_id)\n                    SELECT (SELECT id FROM insert_components), id\n                    FROM insert_scores\n                )\n                INSERT INTO records_components (record_id, component_id)\n                SELECT $5, (SELECT id from insert_components) \n                ",
-  "describe": {
-    "columns": [],
-    "parameters": {
-      "Left": [
-        "Text",
-        "Int8",
-        "TextArray",
-        "Float8Array",
-        "Int4"
-      ]
-    },
-    "nullable": []
-  },
-  "hash": "50c6776ca37b9b56b92848573f32d72f44631c9ff702bb468dc7a7f5520a6f48"
-}
diff --git a/auditor/.sqlx/query-520d88dec0363b824ca4af064510c2e837819eb7877850ff8077643a5bdbe3e2.json b/auditor/.sqlx/query-520d88dec0363b824ca4af064510c2e837819eb7877850ff8077643a5bdbe3e2.json
deleted file mode 100644
index 979772c8e..000000000
--- a/auditor/.sqlx/query-520d88dec0363b824ca4af064510c2e837819eb7877850ff8077643a5bdbe3e2.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "\n        INSERT INTO accounting (\n            record_id, start_time, stop_time, runtime, updated_at\n        )\n        SELECT * FROM UNNEST($1::text[], $2::timestamptz[], $3::timestamptz[], $4::bigint[], $5::timestamptz[])\n        RETURNING id;\n        ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "id",
-        "type_info": "Int4"
-      }
-    ],
-    "parameters": {
-      "Left": [
-        "TextArray",
-        "TimestamptzArray",
-        "TimestamptzArray",
-        "Int8Array",
-        "TimestamptzArray"
-      ]
-    },
-    "nullable": [
-      false
-    ]
-  },
-  "hash": "520d88dec0363b824ca4af064510c2e837819eb7877850ff8077643a5bdbe3e2"
-}
diff --git a/auditor/.sqlx/query-0de2b3aa2731428f5c749d7c166ab8be945eb01c6515fb3db329e45e39fd7d12.json b/auditor/.sqlx/query-5b634a506d39595160459fb8a8ea3a9760372c1fc3dd14e8c086a2f91d7177d6.json
similarity index 60%
rename from auditor/.sqlx/query-0de2b3aa2731428f5c749d7c166ab8be945eb01c6515fb3db329e45e39fd7d12.json
rename to auditor/.sqlx/query-5b634a506d39595160459fb8a8ea3a9760372c1fc3dd14e8c086a2f91d7177d6.json
index b534ac430..baee16ce6 100644
--- a/auditor/.sqlx/query-0de2b3aa2731428f5c749d7c166ab8be945eb01c6515fb3db329e45e39fd7d12.json
+++ b/auditor/.sqlx/query-5b634a506d39595160459fb8a8ea3a9760372c1fc3dd14e8c086a2f91d7177d6.json
@@ -1,6 +1,6 @@
 {
   "db_name": "PostgreSQL",
-  "query": "\n        SELECT start_time\n        FROM accounting \n        WHERE record_id = $1\n        ",
+  "query": "\n        SELECT start_time\n        FROM auditor \n        WHERE record_id = $1\n        ",
   "describe": {
     "columns": [
       {
@@ -18,5 +18,5 @@
       false
     ]
   },
-  "hash": "0de2b3aa2731428f5c749d7c166ab8be945eb01c6515fb3db329e45e39fd7d12"
+  "hash": "5b634a506d39595160459fb8a8ea3a9760372c1fc3dd14e8c086a2f91d7177d6"
 }
diff --git a/auditor/.sqlx/query-622a95c18c6d5bb7d69e5f557e4742a07db9389fb810005f9e8bc142686c7846.json b/auditor/.sqlx/query-622a95c18c6d5bb7d69e5f557e4742a07db9389fb810005f9e8bc142686c7846.json
deleted file mode 100644
index ea9bc2d61..000000000
--- a/auditor/.sqlx/query-622a95c18c6d5bb7d69e5f557e4742a07db9389fb810005f9e8bc142686c7846.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "SELECT value as \"name!\", count(*) as \"num!\"\n               FROM meta\n               WHERE key = $1 \n               GROUP BY value;\n            ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "name!",
-        "type_info": "Text"
-      },
-      {
-        "ordinal": 1,
-        "name": "num!",
-        "type_info": "Int8"
-      }
-    ],
-    "parameters": {
-      "Left": [
-        "Text"
-      ]
-    },
-    "nullable": [
-      false,
-      null
-    ]
-  },
-  "hash": "622a95c18c6d5bb7d69e5f557e4742a07db9389fb810005f9e8bc142686c7846"
-}
diff --git a/auditor/.sqlx/query-6447284378b0fd990f13ee4cdb6eeaecd4c55df263b7e892e22e073ac73a276c.json b/auditor/.sqlx/query-6447284378b0fd990f13ee4cdb6eeaecd4c55df263b7e892e22e073ac73a276c.json
new file mode 100644
index 000000000..733b4fa75
--- /dev/null
+++ b/auditor/.sqlx/query-6447284378b0fd990f13ee4cdb6eeaecd4c55df263b7e892e22e073ac73a276c.json
@@ -0,0 +1,52 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "SELECT record_id,\n                  meta,\n                  components,\n                  start_time,\n                  stop_time,\n                  runtime\n           FROM auditor\n           WHERE record_id = $1\n        ",
+  "describe": {
+    "columns": [
+      {
+        "ordinal": 0,
+        "name": "record_id",
+        "type_info": "Text"
+      },
+      {
+        "ordinal": 1,
+        "name": "meta",
+        "type_info": "Jsonb"
+      },
+      {
+        "ordinal": 2,
+        "name": "components",
+        "type_info": "Jsonb"
+      },
+      {
+        "ordinal": 3,
+        "name": "start_time",
+        "type_info": "Timestamptz"
+      },
+      {
+        "ordinal": 4,
+        "name": "stop_time",
+        "type_info": "Timestamptz"
+      },
+      {
+        "ordinal": 5,
+        "name": "runtime",
+        "type_info": "Int8"
+      }
+    ],
+    "parameters": {
+      "Left": [
+        "Text"
+      ]
+    },
+    "nullable": [
+      false,
+      true,
+      true,
+      false,
+      true,
+      true
+    ]
+  },
+  "hash": "6447284378b0fd990f13ee4cdb6eeaecd4c55df263b7e892e22e073ac73a276c"
+}
diff --git a/auditor/.sqlx/query-6ba76bcc9e877d3b6e353a4b85a1115d4821d4304b91593614ea39597ae29bc1.json b/auditor/.sqlx/query-6ba76bcc9e877d3b6e353a4b85a1115d4821d4304b91593614ea39597ae29bc1.json
deleted file mode 100644
index 0442927d1..000000000
--- a/auditor/.sqlx/query-6ba76bcc9e877d3b6e353a4b85a1115d4821d4304b91593614ea39597ae29bc1.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "\n            WITH insert_components AS (\n                INSERT INTO components (name, amount)\n                VALUES ($1, $2)\n                RETURNING id\n            ),\n            insert_scores AS (\n                INSERT INTO scores (name, value)\n                SELECT * FROM UNNEST($3::text[], $4::double precision[])\n                -- Update if already in table. This isn't great, but \n                -- otherwise RETURNING won't return anything.\n                ON CONFLICT (name, value) DO UPDATE\n                SET value = EXCLUDED.value, name = EXCLUDED.name\n                RETURNING id\n            ),\n            insert_components_scores AS (\n                INSERT INTO components_scores (component_id, score_id)\n                SELECT (SELECT id FROM insert_components), id\n                FROM insert_scores\n            )\n            INSERT INTO records_components (record_id, component_id)\n            SELECT $5, (SELECT id from insert_components) \n            ",
-  "describe": {
-    "columns": [],
-    "parameters": {
-      "Left": [
-        "Text",
-        "Int8",
-        "TextArray",
-        "Float8Array",
-        "Int4"
-      ]
-    },
-    "nullable": []
-  },
-  "hash": "6ba76bcc9e877d3b6e353a4b85a1115d4821d4304b91593614ea39597ae29bc1"
-}
diff --git a/auditor/.sqlx/query-6c7c9b840bd8bf36dd80484f6c82787423b2304ccff83a98a57ec398c1b46ddb.json b/auditor/.sqlx/query-6c7c9b840bd8bf36dd80484f6c82787423b2304ccff83a98a57ec398c1b46ddb.json
deleted file mode 100644
index d4804d9b0..000000000
--- a/auditor/.sqlx/query-6c7c9b840bd8bf36dd80484f6c82787423b2304ccff83a98a57ec398c1b46ddb.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "SELECT value as \"name!\", count(*) as \"num!\"\n               FROM meta\n               WHERE key = $1\n               GROUP BY value;\n            ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "name!",
-        "type_info": "Text"
-      },
-      {
-        "ordinal": 1,
-        "name": "num!",
-        "type_info": "Int8"
-      }
-    ],
-    "parameters": {
-      "Left": [
-        "Text"
-      ]
-    },
-    "nullable": [
-      false,
-      null
-    ]
-  },
-  "hash": "6c7c9b840bd8bf36dd80484f6c82787423b2304ccff83a98a57ec398c1b46ddb"
-}
diff --git a/auditor/.sqlx/query-80a4ffc9c5af2cf8570c4543df24cc9fc9604f98bc4716ae4f99479af245774e.json b/auditor/.sqlx/query-80a4ffc9c5af2cf8570c4543df24cc9fc9604f98bc4716ae4f99479af245774e.json
deleted file mode 100644
index 076466e54..000000000
--- a/auditor/.sqlx/query-80a4ffc9c5af2cf8570c4543df24cc9fc9604f98bc4716ae4f99479af245774e.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "\n        UPDATE accounting\n        SET stop_time = $2,\n            runtime = $3,\n            updated_at = $4\n        WHERE\n            record_id = $1\n        ",
-  "describe": {
-    "columns": [],
-    "parameters": {
-      "Left": [
-        "Text",
-        "Timestamptz",
-        "Int8",
-        "Timestamptz"
-      ]
-    },
-    "nullable": []
-  },
-  "hash": "80a4ffc9c5af2cf8570c4543df24cc9fc9604f98bc4716ae4f99479af245774e"
-}
diff --git a/auditor/.sqlx/query-9f11dbac5821c4debdfc4081fece6ef310623d73c16433c49a441c2d3eee0894.json b/auditor/.sqlx/query-9f11dbac5821c4debdfc4081fece6ef310623d73c16433c49a441c2d3eee0894.json
new file mode 100644
index 000000000..50d1e228b
--- /dev/null
+++ b/auditor/.sqlx/query-9f11dbac5821c4debdfc4081fece6ef310623d73c16433c49a441c2d3eee0894.json
@@ -0,0 +1,26 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "\n            SELECT jsonb_array_elements_text(meta->'user_id') AS \"name!\", COUNT(*) AS \"num!\"\n            FROM auditor\n            GROUP BY jsonb_array_elements_text(meta->'user_id');\n            ",
+  "describe": {
+    "columns": [
+      {
+        "ordinal": 0,
+        "name": "name!",
+        "type_info": "Text"
+      },
+      {
+        "ordinal": 1,
+        "name": "num!",
+        "type_info": "Int8"
+      }
+    ],
+    "parameters": {
+      "Left": []
+    },
+    "nullable": [
+      null,
+      null
+    ]
+  },
+  "hash": "9f11dbac5821c4debdfc4081fece6ef310623d73c16433c49a441c2d3eee0894"
+}
diff --git a/auditor/.sqlx/query-b4e6b925764d8bdabd3aa358f357fc59f0e71b39e06c82e332185cc9ab0b04ed.json b/auditor/.sqlx/query-b4e6b925764d8bdabd3aa358f357fc59f0e71b39e06c82e332185cc9ab0b04ed.json
deleted file mode 100644
index f071103a0..000000000
--- a/auditor/.sqlx/query-b4e6b925764d8bdabd3aa358f357fc59f0e71b39e06c82e332185cc9ab0b04ed.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "SELECT a.record_id,\n                          m.meta as \"meta: Vec<(String, Vec<String>)>\",\n                          css.components as \"components: Vec<Component>\",\n                          a.start_time as \"start_time?\",\n                          a.stop_time,\n                          a.runtime\n                   FROM accounting a\n                   LEFT JOIN (\n                       WITH subquery AS (\n                           SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values\n                           FROM meta as m\n                           GROUP BY m.record_id, m.key\n                       )\n                       SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta\n                       FROM subquery as s\n                       GROUP BY s.record_id\n                       ) m ON m.record_id = a.record_id\n                   LEFT JOIN (\n                       WITH subquery AS (\n                          SELECT \n                              c.id as cid,\n                              COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores\n                          FROM components as c\n                          LEFT JOIN components_scores as cs\n                          ON c.id = cs.component_id\n                          LEFT JOIN scores as s\n                          ON cs.score_id = s.id\n                          GROUP BY c.id\n                       )\n                       SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components\n                       FROM records_components AS rc\n                       LEFT JOIN components as c\n                       ON rc.component_id = c.id\n                       LEFT JOIN subquery AS sq\n                       ON sq.cid = rc.component_id\n                       GROUP BY rc.record_id\n                   ) css ON css.id = a.id\n                   WHERE a.start_time > $1 and a.runtime IS NOT NULL\n                   ORDER BY a.stop_time\n               ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "record_id",
-        "type_info": "Text"
-      },
-      {
-        "ordinal": 1,
-        "name": "meta: Vec<(String, Vec<String>)>",
-        "type_info": "RecordArray"
-      },
-      {
-        "ordinal": 2,
-        "name": "components: Vec<Component>",
-        "type_info": {
-          "Custom": {
-            "name": "_component",
-            "kind": {
-              "Array": {
-                "Custom": {
-                  "name": "component",
-                  "kind": {
-                    "Composite": [
-                      [
-                        "name",
-                        "Text"
-                      ],
-                      [
-                        "amount",
-                        "Int8"
-                      ],
-                      [
-                        "scores",
-                        {
-                          "Custom": {
-                            "name": "_score",
-                            "kind": {
-                              "Array": {
-                                "Custom": {
-                                  "name": "score",
-                                  "kind": {
-                                    "Composite": [
-                                      [
-                                        "name",
-                                        "Text"
-                                      ],
-                                      [
-                                        "value",
-                                        "Float8"
-                                      ]
-                                    ]
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        }
-                      ]
-                    ]
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      {
-        "ordinal": 3,
-        "name": "start_time?",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 4,
-        "name": "stop_time",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 5,
-        "name": "runtime",
-        "type_info": "Int8"
-      }
-    ],
-    "parameters": {
-      "Left": [
-        "Timestamptz"
-      ]
-    },
-    "nullable": [
-      false,
-      null,
-      null,
-      false,
-      true,
-      true
-    ]
-  },
-  "hash": "b4e6b925764d8bdabd3aa358f357fc59f0e71b39e06c82e332185cc9ab0b04ed"
-}
diff --git a/auditor/.sqlx/query-c7ec1adffa973855f43122df7ba8abe8e2826fb8ef5dfd2e900d1d2c09d6ead1.json b/auditor/.sqlx/query-c7ec1adffa973855f43122df7ba8abe8e2826fb8ef5dfd2e900d1d2c09d6ead1.json
new file mode 100644
index 000000000..249b8fcb8
--- /dev/null
+++ b/auditor/.sqlx/query-c7ec1adffa973855f43122df7ba8abe8e2826fb8ef5dfd2e900d1d2c09d6ead1.json
@@ -0,0 +1,28 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "\n        INSERT INTO auditor (\n            record_id, start_time, stop_time, meta, components, runtime, updated_at\n        )\n        SELECT * FROM UNNEST($1::text[], $2::timestamptz[], $3::timestamptz[], $4::jsonb[], $5::jsonb[],  $6::bigint[], $7::timestamptz[])\n        RETURNING id;\n        ",
+  "describe": {
+    "columns": [
+      {
+        "ordinal": 0,
+        "name": "id",
+        "type_info": "Int4"
+      }
+    ],
+    "parameters": {
+      "Left": [
+        "TextArray",
+        "TimestamptzArray",
+        "TimestamptzArray",
+        "JsonbArray",
+        "JsonbArray",
+        "Int8Array",
+        "TimestamptzArray"
+      ]
+    },
+    "nullable": [
+      false
+    ]
+  },
+  "hash": "c7ec1adffa973855f43122df7ba8abe8e2826fb8ef5dfd2e900d1d2c09d6ead1"
+}
diff --git a/auditor/.sqlx/query-df51c1b3b87d9315ba94de95a29870f5fc2f0df1cd42243b1727276af7a69503.json b/auditor/.sqlx/query-df51c1b3b87d9315ba94de95a29870f5fc2f0df1cd42243b1727276af7a69503.json
new file mode 100644
index 000000000..a739f20a6
--- /dev/null
+++ b/auditor/.sqlx/query-df51c1b3b87d9315ba94de95a29870f5fc2f0df1cd42243b1727276af7a69503.json
@@ -0,0 +1,26 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "\n            SELECT jsonb_array_elements_text(meta->'group_id') AS \"name!\", COUNT(*) AS \"num!\"\n            FROM auditor\n            GROUP BY jsonb_array_elements_text(meta->'group_id');\n            ",
+  "describe": {
+    "columns": [
+      {
+        "ordinal": 0,
+        "name": "name!",
+        "type_info": "Text"
+      },
+      {
+        "ordinal": 1,
+        "name": "num!",
+        "type_info": "Int8"
+      }
+    ],
+    "parameters": {
+      "Left": []
+    },
+    "nullable": [
+      null,
+      null
+    ]
+  },
+  "hash": "df51c1b3b87d9315ba94de95a29870f5fc2f0df1cd42243b1727276af7a69503"
+}
diff --git a/auditor/.sqlx/query-e21896f3c7d1bbbef588d50794a4cd5ecd7f5f5bf51945bc2076ebd104ecbfbc.json b/auditor/.sqlx/query-e21896f3c7d1bbbef588d50794a4cd5ecd7f5f5bf51945bc2076ebd104ecbfbc.json
new file mode 100644
index 000000000..167483b76
--- /dev/null
+++ b/auditor/.sqlx/query-e21896f3c7d1bbbef588d50794a4cd5ecd7f5f5bf51945bc2076ebd104ecbfbc.json
@@ -0,0 +1,50 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "SELECT record_id,\n                  meta,\n                  components,\n                  start_time,\n                  stop_time,\n                  runtime\n           FROM auditor\n           ORDER BY stop_time\n        ",
+  "describe": {
+    "columns": [
+      {
+        "ordinal": 0,
+        "name": "record_id",
+        "type_info": "Text"
+      },
+      {
+        "ordinal": 1,
+        "name": "meta",
+        "type_info": "Jsonb"
+      },
+      {
+        "ordinal": 2,
+        "name": "components",
+        "type_info": "Jsonb"
+      },
+      {
+        "ordinal": 3,
+        "name": "start_time",
+        "type_info": "Timestamptz"
+      },
+      {
+        "ordinal": 4,
+        "name": "stop_time",
+        "type_info": "Timestamptz"
+      },
+      {
+        "ordinal": 5,
+        "name": "runtime",
+        "type_info": "Int8"
+      }
+    ],
+    "parameters": {
+      "Left": []
+    },
+    "nullable": [
+      false,
+      true,
+      true,
+      false,
+      true,
+      true
+    ]
+  },
+  "hash": "e21896f3c7d1bbbef588d50794a4cd5ecd7f5f5bf51945bc2076ebd104ecbfbc"
+}
diff --git a/auditor/.sqlx/query-e6c7e05a8f9aac8353000db25e8ac0bc54f7ff067ab70715b8659cb835f45af6.json b/auditor/.sqlx/query-e6c7e05a8f9aac8353000db25e8ac0bc54f7ff067ab70715b8659cb835f45af6.json
deleted file mode 100644
index 43d30b56c..000000000
--- a/auditor/.sqlx/query-e6c7e05a8f9aac8353000db25e8ac0bc54f7ff067ab70715b8659cb835f45af6.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "SELECT a.record_id,\n                          m.meta as \"meta: Vec<(String, Vec<String>)>\",\n                          css.components as \"components: Vec<Component>\",\n                          a.start_time as \"start_time?\",\n                          a.stop_time,\n                          a.runtime\n                   FROM accounting a\n                   LEFT JOIN (\n                       WITH subquery AS (\n                           SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values\n                           FROM meta as m\n                           GROUP BY m.record_id, m.key\n                       )\n                       SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta\n                       FROM subquery as s\n                       GROUP BY s.record_id\n                       ) m ON m.record_id = a.record_id\n                   LEFT JOIN (\n                       WITH subquery AS (\n                          SELECT \n                              c.id as cid,\n                              COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores\n                          FROM components as c\n                          LEFT JOIN components_scores as cs\n                          ON c.id = cs.component_id\n                          LEFT JOIN scores as s\n                          ON cs.score_id = s.id\n                          GROUP BY c.id\n                       )\n                       SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components\n                       FROM records_components AS rc\n                       LEFT JOIN components as c\n                       ON rc.component_id = c.id\n                       LEFT JOIN subquery AS sq\n                       ON sq.cid = rc.component_id\n                       GROUP BY rc.record_id\n                   ) css ON css.id = a.id\n                   WHERE a.record_id = $1\n               ",
-  "describe": {
-    "columns": [
-      {
-        "ordinal": 0,
-        "name": "record_id",
-        "type_info": "Text"
-      },
-      {
-        "ordinal": 1,
-        "name": "meta: Vec<(String, Vec<String>)>",
-        "type_info": "RecordArray"
-      },
-      {
-        "ordinal": 2,
-        "name": "components: Vec<Component>",
-        "type_info": {
-          "Custom": {
-            "name": "_component",
-            "kind": {
-              "Array": {
-                "Custom": {
-                  "name": "component",
-                  "kind": {
-                    "Composite": [
-                      [
-                        "name",
-                        "Text"
-                      ],
-                      [
-                        "amount",
-                        "Int8"
-                      ],
-                      [
-                        "scores",
-                        {
-                          "Custom": {
-                            "name": "_score",
-                            "kind": {
-                              "Array": {
-                                "Custom": {
-                                  "name": "score",
-                                  "kind": {
-                                    "Composite": [
-                                      [
-                                        "name",
-                                        "Text"
-                                      ],
-                                      [
-                                        "value",
-                                        "Float8"
-                                      ]
-                                    ]
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        }
-                      ]
-                    ]
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      {
-        "ordinal": 3,
-        "name": "start_time?",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 4,
-        "name": "stop_time",
-        "type_info": "Timestamptz"
-      },
-      {
-        "ordinal": 5,
-        "name": "runtime",
-        "type_info": "Int8"
-      }
-    ],
-    "parameters": {
-      "Left": [
-        "Text"
-      ]
-    },
-    "nullable": [
-      false,
-      null,
-      null,
-      false,
-      true,
-      true
-    ]
-  },
-  "hash": "e6c7e05a8f9aac8353000db25e8ac0bc54f7ff067ab70715b8659cb835f45af6"
-}
diff --git a/auditor/.sqlx/query-f9013f697e7b81594947530172248a0400cfbf7e316d0224b4692475a12a7d7f.json b/auditor/.sqlx/query-f9013f697e7b81594947530172248a0400cfbf7e316d0224b4692475a12a7d7f.json
deleted file mode 100644
index 424e95627..000000000
--- a/auditor/.sqlx/query-f9013f697e7b81594947530172248a0400cfbf7e316d0224b4692475a12a7d7f.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "db_name": "PostgreSQL",
-  "query": "\n            INSERT INTO meta (record_id, key, value)\n            SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])\n            ",
-  "describe": {
-    "columns": [],
-    "parameters": {
-      "Left": [
-        "TextArray",
-        "TextArray",
-        "TextArray"
-      ]
-    },
-    "nullable": []
-  },
-  "hash": "f9013f697e7b81594947530172248a0400cfbf7e316d0224b4692475a12a7d7f"
-}
diff --git a/auditor/.sqlx/query-fd0276b7b26e8fee23c01c8552cd8f4391fb2f9de1cdd6e6501e8ee5647cacbf.json b/auditor/.sqlx/query-fd0276b7b26e8fee23c01c8552cd8f4391fb2f9de1cdd6e6501e8ee5647cacbf.json
new file mode 100644
index 000000000..4c46745dc
--- /dev/null
+++ b/auditor/.sqlx/query-fd0276b7b26e8fee23c01c8552cd8f4391fb2f9de1cdd6e6501e8ee5647cacbf.json
@@ -0,0 +1,17 @@
+{
+  "db_name": "PostgreSQL",
+  "query": "\n        UPDATE auditor\n        SET stop_time = $2,\n            runtime = $3,\n            updated_at = $4\n        WHERE\n            record_id = $1\n        ",
+  "describe": {
+    "columns": [],
+    "parameters": {
+      "Left": [
+        "Text",
+        "Timestamptz",
+        "Int8",
+        "Timestamptz"
+      ]
+    },
+    "nullable": []
+  },
+  "hash": "fd0276b7b26e8fee23c01c8552cd8f4391fb2f9de1cdd6e6501e8ee5647cacbf"
+}
diff --git a/auditor/Cargo.toml b/auditor/Cargo.toml
index 5a0c157f6..d86f8115e 100644
--- a/auditor/Cargo.toml
+++ b/auditor/Cargo.toml
@@ -78,7 +78,8 @@ features = [
     "postgres",
     "uuid",
     "chrono",
-    "migrate"
+    "migrate",
+    "json"
 ]
 
 [dev-dependencies]
diff --git a/auditor/src/domain/record.rs b/auditor/src/domain/record.rs
index 921bef117..05f471aed 100644
--- a/auditor/src/domain/record.rs
+++ b/auditor/src/domain/record.rs
@@ -224,11 +224,11 @@ pub struct Record {
 }
 
 #[doc(hidden)]
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, sqlx::FromRow)]
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, sqlx::FromRow)]
 pub struct RecordDatabase {
     pub record_id: String,
-    pub meta: Option<Vec<(String, Vec<String>)>>,
-    pub components: Option<Vec<Component>>,
+    pub meta: Option<serde_json::Value>,
+    pub components: Option<serde_json::Value>,
     pub start_time: Option<DateTime<Utc>>,
     pub stop_time: Option<DateTime<Utc>>,
     pub runtime: Option<i64>,
@@ -663,7 +663,13 @@ impl TryFrom<RecordDatabase> for Record {
             runtime,
         } = other;
         let meta = if let Some(meta) = meta {
-            Some(meta.try_into()?)
+            serde_json::from_value(meta).ok()
+        } else {
+            None
+        };
+
+        let components = if let Some(components) = components {
+            serde_json::from_value(components).ok()
         } else {
             None
         };
diff --git a/auditor/src/metrics/database.rs b/auditor/src/metrics/database.rs
index 53cf1b41d..016b1a59b 100644
--- a/auditor/src/metrics/database.rs
+++ b/auditor/src/metrics/database.rs
@@ -96,7 +96,7 @@ impl DatabaseMetricsWatcher {
 
     #[tracing::instrument(name = "Update record count for database metrics", skip(self))]
     async fn update_record_count(&self) -> Result<(), anyhow::Error> {
-        let num = sqlx::query_scalar!(r#"SELECT count(*) as "count!" FROM accounting;"#)
+        let num = sqlx::query_scalar!(r#"SELECT count(*) as "count!" FROM auditor;"#)
             .fetch_one(&self.db_pool)
             .await?;
         let mut data_lock = self.data.lock().unwrap();
@@ -108,12 +108,11 @@ impl DatabaseMetricsWatcher {
     async fn update_record_count_per_site(&self) -> Result<(), anyhow::Error> {
         let per_site: HashMap<String, i64> = sqlx::query_as!(
             AggregatedColumns,
-            r#"SELECT value as "name!", count(*) as "num!"
-               FROM meta
-               WHERE key = $1 
-               GROUP BY value;
-            "#,
-            "site_id"
+            r#"
+            SELECT jsonb_array_elements_text(meta->'site_id') AS "name!", COUNT(*) AS "num!"
+            FROM auditor
+            GROUP BY jsonb_array_elements_text(meta->'site_id');
+            "#
         )
         .fetch_all(&self.db_pool)
         .await?
@@ -133,12 +132,11 @@ impl DatabaseMetricsWatcher {
     async fn update_record_count_per_group(&self) -> Result<(), anyhow::Error> {
         let per_group: HashMap<String, i64> = sqlx::query_as!(
             AggregatedColumns,
-            r#"SELECT value as "name!", count(*) as "num!"
-               FROM meta
-               WHERE key = $1
-               GROUP BY value;
-            "#,
-            "group_id"
+            r#"
+            SELECT jsonb_array_elements_text(meta->'group_id') AS "name!", COUNT(*) AS "num!"
+            FROM auditor
+            GROUP BY jsonb_array_elements_text(meta->'group_id');
+            "#
         )
         .fetch_all(&self.db_pool)
         .await?
@@ -155,12 +153,11 @@ impl DatabaseMetricsWatcher {
     async fn update_record_count_per_user(&self) -> Result<(), anyhow::Error> {
         let per_user: HashMap<String, i64> = sqlx::query_as!(
             AggregatedColumns,
-            r#"SELECT value as "name!", count(*) as "num!"
-               FROM meta
-               WHERE key = $1
-               GROUP BY value;
-            "#,
-            "user_id"
+            r#"
+            SELECT jsonb_array_elements_text(meta->'user_id') AS "name!", COUNT(*) AS "num!"
+            FROM auditor
+            GROUP BY jsonb_array_elements_text(meta->'user_id');
+            "#
         )
         .fetch_all(&self.db_pool)
         .await?
diff --git a/auditor/src/routes/add.rs b/auditor/src/routes/add.rs
index 6f6576f77..ed1e4d887 100644
--- a/auditor/src/routes/add.rs
+++ b/auditor/src/routes/add.rs
@@ -9,6 +9,7 @@ use crate::constants::{ERR_RECORD_EXISTS, ERR_UNEXPECTED_ERROR};
 use crate::domain::RecordAdd;
 use actix_web::{web, HttpResponse, ResponseError};
 use chrono::Utc;
+use serde_json::Value;
 use sqlx::PgPool;
 
 #[derive(thiserror::Error)]
@@ -89,91 +90,26 @@ pub async fn add_record(record: &RecordAdd, pool: &PgPool) -> Result<(), AddReco
         Err(e) => return Err(AddRecordError(e)),
     };
 
-    let id = sqlx::query_unchecked!(
+    sqlx::query_unchecked!(
         r#"
-        INSERT INTO accounting (
-            record_id, start_time, stop_time, runtime, updated_at
+        INSERT INTO auditor (
+            record_id, start_time, stop_time, meta, components, runtime, updated_at
         )
-        VALUES ($1, $2, $3, $4, $5)
+        VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING id;
         "#,
         record.record_id.as_ref(),
         record.start_time,
         record.stop_time,
+        serde_json::to_value(&record.meta).unwrap_or_else(|_| serde_json::Value::Null),
+        serde_json::to_value(&record.components).unwrap_or_else(|_| serde_json::Value::Null),
         runtime,
         Utc::now()
     )
     .fetch_optional(&mut *transaction)
     .await
     .map_err(AddRecordError)?
-    .ok_or_else(|| AddRecordError(sqlx::Error::RowNotFound))?
-    .id;
-
-    for component in record.components.iter() {
-        let (names, scores): (Vec<String>, Vec<f64>) = component
-            .scores
-            .iter()
-            .map(|s| (s.name.as_ref().to_string(), s.value.as_ref()))
-            .unzip();
-
-        sqlx::query_unchecked!(
-            r#"
-            WITH insert_components AS (
-                INSERT INTO components (name, amount)
-                VALUES ($1, $2)
-                RETURNING id
-            ),
-            insert_scores AS (
-                INSERT INTO scores (name, value)
-                SELECT * FROM UNNEST($3::text[], $4::double precision[])
-                -- Update if already in table. This isn't great, but 
-                -- otherwise RETURNING won't return anything.
-                ON CONFLICT (name, value) DO UPDATE
-                SET value = EXCLUDED.value, name = EXCLUDED.name
-                RETURNING id
-            ),
-            insert_components_scores AS (
-                INSERT INTO components_scores (component_id, score_id)
-                SELECT (SELECT id FROM insert_components), id
-                FROM insert_scores
-            )
-            INSERT INTO records_components (record_id, component_id)
-            SELECT $5, (SELECT id from insert_components) 
-            "#,
-            component.name.as_ref(),
-            component.amount,
-            &names[..],
-            &scores[..],
-            id,
-        )
-        .execute(&mut *transaction)
-        .await
-        .map_err(AddRecordError)?;
-    }
-
-    if let Some(data) = record.meta.as_ref() {
-        let data = data.to_vec();
-
-        let (rid, names, values): (Vec<String>, Vec<String>, Vec<String>) =
-            itertools::multiunzip(data.into_iter().flat_map(|(k, v)| {
-                v.into_iter()
-                    .map(|v| (record.record_id.as_ref().to_string(), k.clone(), v))
-                    .collect::<Vec<_>>()
-            }));
-
-        sqlx::query!(
-            r#"
-            INSERT INTO meta (record_id, key, value)
-            SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])
-            "#,
-            &rid[..],
-            &names[..],
-            &values[..],
-        )
-        .execute(&mut *transaction)
-        .await
-        .map_err(AddRecordError)?;
-    }
+    .ok_or_else(|| AddRecordError(sqlx::Error::RowNotFound))?;
 
     if let Err(e) = transaction.commit().await {
         Err(AddRecordError(e))
@@ -221,17 +157,28 @@ pub async fn bulk_insert(records: &[RecordAdd], pool: &PgPool) -> Result<(), Add
         .collect();
     let updated_at_vec: Vec<_> = std::iter::repeat(Utc::now()).take(records.len()).collect();
 
-    let ids = sqlx::query_unchecked!(
+    let meta_values: Vec<Value> = records
+        .iter()
+        .map(|r| serde_json::to_value(&r.meta).unwrap_or(serde_json::Value::Null))
+        .collect();
+    let component_values: Vec<Value> = records
+        .iter()
+        .map(|r| serde_json::to_value(&r.components).unwrap_or(serde_json::Value::Null))
+        .collect();
+
+    sqlx::query_unchecked!(
         r#"
-        INSERT INTO accounting (
-            record_id, start_time, stop_time, runtime, updated_at
+        INSERT INTO auditor (
+            record_id, start_time, stop_time, meta, components, runtime, updated_at
         )
-        SELECT * FROM UNNEST($1::text[], $2::timestamptz[], $3::timestamptz[], $4::bigint[], $5::timestamptz[])
+        SELECT * FROM UNNEST($1::text[], $2::timestamptz[], $3::timestamptz[], $4::jsonb[], $5::jsonb[],  $6::bigint[], $7::timestamptz[])
         RETURNING id;
         "#,
         &record_ids[..],
         &start_times[..],
         &stop_times[..],
+        &meta_values[..],
+        &component_values[..],
         &runtimes[..],
         &updated_at_vec[..],
     )
@@ -239,74 +186,6 @@ pub async fn bulk_insert(records: &[RecordAdd], pool: &PgPool) -> Result<(), Add
     .await
     .map_err(AddRecordError)?;
 
-    for (record, id) in records.iter().zip(ids.iter()) {
-        for component in record.components.iter() {
-            let (names, scores): (Vec<String>, Vec<f64>) = component
-                .scores
-                .iter()
-                .map(|s| (s.name.as_ref().to_string(), s.value.as_ref()))
-                .unzip();
-
-            sqlx::query_unchecked!(
-                r#"
-                WITH insert_components AS (
-                    INSERT INTO components (name, amount)
-                    VALUES ($1, $2)
-                    RETURNING id
-                ),
-                insert_scores AS (
-                    INSERT INTO scores (name, value)
-                    SELECT * FROM UNNEST($3::text[], $4::double precision[])
-                    -- Update if already in table. This isn't great, but 
-                    -- otherwise RETURNING won't return anything.
-                    ON CONFLICT (name, value) DO UPDATE
-                    SET value = EXCLUDED.value, name = EXCLUDED.name
-                    RETURNING id
-                ),
-                insert_components_scores AS (
-                    INSERT INTO components_scores (component_id, score_id)
-                    SELECT (SELECT id FROM insert_components), id
-                    FROM insert_scores
-                )
-                INSERT INTO records_components (record_id, component_id)
-                SELECT $5, (SELECT id from insert_components) 
-                "#,
-                component.name.as_ref(),
-                component.amount,
-                &names[..],
-                &scores[..],
-                &id.id,
-            )
-            .execute(&mut *transaction)
-            .await
-            .map_err(AddRecordError)?;
-        }
-
-        if let Some(data) = record.meta.as_ref() {
-            let data = data.to_vec();
-
-            let (rid, names, values): (Vec<String>, Vec<String>, Vec<String>) =
-                itertools::multiunzip(data.into_iter().flat_map(|(k, v)| {
-                    v.into_iter()
-                        .map(|v| (record.record_id.as_ref().to_string(), k.clone(), v))
-                        .collect::<Vec<_>>()
-                }));
-
-            sqlx::query!(
-                r#"
-                INSERT INTO meta (record_id, key, value)
-                SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])
-                "#,
-                &rid[..],
-                &names[..],
-                &values[..],
-            )
-            .execute(&mut *transaction)
-            .await
-            .map_err(AddRecordError)?;
-        }
-    }
-
     if let Err(e) = transaction.commit().await {
         return Err(AddRecordError(e));
     } else {
diff --git a/auditor/src/routes/advanced_record_filters.rs b/auditor/src/routes/advanced_record_filters.rs
index 1cb8c14fb..b5a5a8c72 100644
--- a/auditor/src/routes/advanced_record_filters.rs
+++ b/auditor/src/routes/advanced_record_filters.rs
@@ -5,7 +5,7 @@
 // http://opensource.org/licenses/MIT>, at your option. This file may not be
 // copied, modified, or distributed except according to those terms.
 
-use crate::domain::{Component, Record, RecordDatabase, ValidAmount, ValidName};
+use crate::domain::{Record, RecordDatabase, ValidAmount, ValidName};
 use chrono::{DateTime, Utc};
 use core::fmt::Debug;
 use sqlx::{PgPool, QueryBuilder, Row};
@@ -88,44 +88,16 @@ pub async fn advanced_record_filtering(
     filters: Filters,
     pool: &PgPool,
 ) -> Result<Vec<Record>, anyhow::Error> {
-    let mut query = QueryBuilder::new("SELECT a.record_id,
-                          m.meta,
-                          css.components,
-                          a.start_time,
-                          a.stop_time,
-                          a.runtime
-                   FROM accounting a
-                   LEFT JOIN (
-                       WITH subquery AS (
-                           SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                           FROM meta as m
-                           GROUP BY m.record_id, m.key
-                       )
-                       SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-                       FROM subquery as s
-                       GROUP BY s.record_id
-                       ) m ON m.record_id = a.record_id
-                   LEFT JOIN (
-                       WITH subquery AS (
-                          SELECT 
-                              c.id as cid,
-                              COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                          FROM components as c
-                          LEFT JOIN components_scores as cs
-                          ON c.id = cs.component_id
-                          LEFT JOIN scores as s
-                          ON cs.score_id = s.id
-                          GROUP BY c.id
-                       )
-                       SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-                       FROM records_components AS rc
-                       LEFT JOIN components as c
-                       ON rc.component_id = c.id
-                       LEFT JOIN subquery AS sq
-                       ON sq.cid = rc.component_id
-                       GROUP BY rc.record_id
-                   ) css ON css.id = a.id
-               ");
+    let mut query = QueryBuilder::new(
+        "SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+               ",
+    );
 
     if filters.start_time.is_some()
         || filters.stop_time.is_some()
@@ -137,7 +109,7 @@ pub async fn advanced_record_filtering(
         query.push(" WHERE ".to_string());
         if let Some(record_id) = &filters.record_id {
             // query string -> a.record_id = '{}' and
-            query.push(" a.record_id = ".to_string());
+            query.push(" record_id = ".to_string());
             query.push_bind(record_id);
             query.push(" and ".to_string());
         }
@@ -146,7 +118,7 @@ pub async fn advanced_record_filtering(
             if let Some(operators) = get_operator(start_time_filters) {
                 for operator in operators {
                     // query string -> a.start_time {} '{}' and
-                    query.push(format!(" a.start_time {} ", operator.0));
+                    query.push(format!(" start_time {} ", operator.0));
                     query.push_bind(operator.1);
                     query.push(" and ".to_string());
                 }
@@ -157,7 +129,7 @@ pub async fn advanced_record_filtering(
             if let Some(operators) = get_operator(stop_time_filters) {
                 for operator in operators {
                     // query string -> a.stop_time {} '{}' and
-                    query.push(format!(" a.stop_time {} ", operator.0));
+                    query.push(format!(" stop_time {} ", operator.0));
                     query.push_bind(operator.1);
                     query.push(" and ".to_string());
                 }
@@ -167,21 +139,24 @@ pub async fn advanced_record_filtering(
         if let Some(meta_filters) = &filters.meta {
             for (key, meta_operator) in meta_filters {
                 if let Some(c) = &meta_operator.c {
-                    // query string -> Array['{}'] = ANY(SELECT r.values FROM unnest(m.meta) AS r(key text, values text[]) WHERE r.key = '{}') and
-                    query.push(" Array[".to_string());
-                    query.push_bind(c);
-                    query.push("] = ANY(SELECT r.values FROM unnest(m.meta) AS r(key text, values text[]) WHERE r.key = ".to_string());
+                    // query string -> meta -> "site_id" @> jsonb_build_array("site1") and
+
+                    query.push(" meta ->  ".to_string());
                     query.push_bind(key);
-                    query.push(" ) ".to_string());
-                    query.push(" and ".to_string());
+                    query.push(" @> jsonb_build_array(".to_string());
+                    query.push_bind(c);
+                    query.push(") ");
+                    query.push(" and ");
                 }
                 if let Some(dnc) = &meta_operator.dnc {
-                    // query string -> (NOT EXISTS (SELECT r.values FROM unnest(m.meta) AS r(key text, values text[]) WHERE r.key = '{}' AND Array['{}'] @> r.values)) and
-                    query.push(" (NOT EXISTS (SELECT r.values FROM unnest(m.meta) AS r(key text, values text[]) WHERE r.key = ".to_string());
+                    // query string -> NOT (meta -> "site_id" @> jsonb_build_array("site_1")) and
+
+                    query.push(" NOT (meta ->  ".to_string());
                     query.push_bind(key);
-                    query.push(" AND Array[".to_string());
+                    query.push(" @> jsonb_build_array(".to_string());
                     query.push_bind(dnc);
-                    query.push("] @> r.values)) and ");
+                    query.push(") ) ");
+                    query.push(" and ");
                 }
             }
         }
@@ -190,15 +165,18 @@ pub async fn advanced_record_filtering(
             for (key, component_operator) in component_filters {
                 if let Some(operators) = get_operator(component_operator) {
                     for operator in operators {
-                        // query string -> EXISTS ( SELECT * FROM unnest(components) AS r WHERE r.name = '{}' and r.amount {} '{}' ) and
-                        query.push(
-                            " EXISTS ( SELECT * FROM unnest(components) AS r WHERE r.name = "
-                                .to_string(),
-                        );
+                        // query string -> components->0->>'name' = "CPU" AND
+                        // (components->0->>'amount')::int >10  and
+
+                        query.push("components->0->>'name' = ");
                         query.push_bind(key);
-                        query.push(format!(" and r.amount {} ", &operator.0));
+                        query.push(format!(
+                            " AND (components->0->>'amount')::int {} ",
+                            &operator.0
+                        ));
                         query.push_bind(operator.1);
-                        query.push(" ) and ".to_string());
+
+                        query.push(" and ".to_string());
                     }
                 }
             }
@@ -212,25 +190,25 @@ pub async fn advanced_record_filtering(
             if let Some(operators) = get_operator(runtime_filters) {
                 for operator in operators {
                     // query string ->  a.runtime {} {} and
-                    query.push(format!(" a.runtime {} ", operator.0));
+                    query.push(format!(" runtime {} ", operator.0));
                     query.push_bind(operator.1);
                     query.push(" and ".to_string());
                 }
             }
         } else {
-            query.push(" a.runtime IS NOT NULL".to_string());
+            query.push(" runtime IS NOT NULL".to_string());
         }
     }
 
     if let Some(sort_by) = &filters.sort_by {
         if let SortOption::ASC(asc) = sort_by {
-            query.push(format!(" ORDER BY a.{} ASC", &asc.to_string()));
+            query.push(format!(" ORDER BY {} ASC", &asc.to_string()));
         }
         if let SortOption::DESC(desc) = sort_by {
-            query.push(format!(" ORDER BY a.{} DESC", &desc.to_string()));
+            query.push(format!(" ORDER BY {} DESC", &desc.to_string()));
         }
     } else {
-        query.push(" ORDER BY a.stop_time ".to_string());
+        query.push(" ORDER BY stop_time ".to_string());
     }
 
     if let Some(limit) = &filters.limit {
@@ -285,22 +263,25 @@ pub async fn advanced_record_filtering(
         .await
         .map_err(GetRecordError)?;
 
-    let result: Vec<RecordDatabase> = rows
+    let result: Vec<Record> = rows
         .iter()
-        .map(|row| RecordDatabase {
+        .map(|row| Record {
             record_id: row.try_get("record_id").unwrap(),
-            meta: row.try_get("meta").ok().unwrap_or(None),
-            components: row.try_get("components").ok().unwrap_or(None),
+            meta: row
+                .try_get("meta")
+                .ok()
+                .and_then(|value| serde_json::from_value(value).ok()),
+            components: row
+                .try_get("components")
+                .ok()
+                .and_then(|value| serde_json::from_value(value).ok()),
             start_time: row.try_get("start_time").ok().unwrap_or(None),
             stop_time: row.try_get("stop_time").ok().unwrap_or(None),
             runtime: row.try_get("runtime").ok().unwrap_or(None),
         })
         .collect();
 
-    result
-        .into_iter()
-        .map(Record::try_from)
-        .collect::<Result<Vec<Record>, anyhow::Error>>()
+    Ok(result)
 }
 
 #[tracing::instrument(name = "Getting one record using record_id", skip(record_id, pool))]
@@ -311,53 +292,23 @@ pub async fn get_one_record(
     let is_valid_record_id = ValidName::parse(record_id.clone().to_string());
     if is_valid_record_id.is_ok() {
         Ok(sqlx::query_as!(
-                RecordDatabase,
-                r#"SELECT a.record_id,
-                          m.meta as "meta: Vec<(String, Vec<String>)>",
-                          css.components as "components: Vec<Component>",
-                          a.start_time as "start_time?",
-                          a.stop_time,
-                          a.runtime
-                   FROM accounting a
-                   LEFT JOIN (
-                       WITH subquery AS (
-                           SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                           FROM meta as m
-                           GROUP BY m.record_id, m.key
-                       )
-                       SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-                       FROM subquery as s
-                       GROUP BY s.record_id
-                       ) m ON m.record_id = a.record_id
-                   LEFT JOIN (
-                       WITH subquery AS (
-                          SELECT 
-                              c.id as cid,
-                              COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                          FROM components as c
-                          LEFT JOIN components_scores as cs
-                          ON c.id = cs.component_id
-                          LEFT JOIN scores as s
-                          ON cs.score_id = s.id
-                          GROUP BY c.id
-                       )
-                       SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-                       FROM records_components AS rc
-                       LEFT JOIN components as c
-                       ON rc.component_id = c.id
-                       LEFT JOIN subquery AS sq
-                       ON sq.cid = rc.component_id
-                       GROUP BY rc.record_id
-                   ) css ON css.id = a.id
-                   WHERE a.record_id = $1
-               "#,
-               &record_id,
-            )
-            .fetch_one(pool)
-            .await
-            .map(Record::try_from)
-            .map_err(GetRecordError)?
-            .ok())
+            RecordDatabase,
+            r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           WHERE record_id = $1
+        "#,
+            &record_id,
+        )
+        .fetch_one(pool)
+        .await
+        .map(Record::try_from)
+        .map_err(GetRecordError)?
+        .ok())
     } else {
         return Ok(None);
     }
diff --git a/auditor/src/routes/get.rs b/auditor/src/routes/get.rs
index 0a5ae9f65..9a947f597 100644
--- a/auditor/src/routes/get.rs
+++ b/auditor/src/routes/get.rs
@@ -5,7 +5,7 @@
 // http://opensource.org/licenses/MIT>, at your option. This file may not be
 // copied, modified, or distributed except according to those terms.
 
-use crate::domain::{Component, Record, RecordDatabase};
+use crate::domain::{Record, RecordDatabase};
 use sqlx::PgPool;
 
 #[derive(thiserror::Error)]
@@ -21,44 +21,14 @@ responseerror_for_error!(GetError, UnexpectedError => INTERNAL_SERVER_ERROR;);
 pub async fn get_records(pool: &PgPool) -> Result<Vec<Record>, anyhow::Error> {
     sqlx::query_as!(
         RecordDatabase,
-        r#"SELECT a.record_id,
-                  m.meta as "meta: Vec<(String, Vec<String>)>",
-                  css.components as "components: Vec<Component>",
-                  a.start_time as "start_time?",
-                  a.stop_time,
-                  a.runtime
-           FROM accounting a
-           LEFT JOIN (
-               WITH subquery AS (
-                   SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                   FROM meta as m
-                   GROUP BY m.record_id, m.key
-               )
-               SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-               FROM subquery as s
-               GROUP BY s.record_id
-               ) m ON m.record_id = a.record_id
-           LEFT JOIN (
-               WITH subquery AS (
-                  SELECT 
-                      c.id as cid,
-                      COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                  FROM components as c
-                  LEFT JOIN components_scores as cs
-                  ON c.id = cs.component_id
-                  LEFT JOIN scores as s
-                  ON cs.score_id = s.id
-                  GROUP BY c.id
-               )
-               SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-               FROM records_components AS rc
-               LEFT JOIN components as c
-               ON rc.component_id = c.id
-               LEFT JOIN subquery AS sq
-               ON sq.cid = rc.component_id
-               GROUP BY rc.record_id
-           ) css ON css.id = a.id
-           ORDER BY a.stop_time
+        r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           ORDER BY stop_time
         "#
     )
     .fetch_all(pool)
diff --git a/auditor/src/routes/update.rs b/auditor/src/routes/update.rs
index 07cebb8ae..4613fdc3f 100644
--- a/auditor/src/routes/update.rs
+++ b/auditor/src/routes/update.rs
@@ -52,7 +52,7 @@ pub async fn update_record(record: &RecordUpdate, pool: &PgPool) -> Result<(), U
     let start_time = sqlx::query!(
         r#"
         SELECT start_time
-        FROM accounting 
+        FROM auditor 
         WHERE record_id = $1
         "#,
         record.record_id.as_ref(),
@@ -69,7 +69,7 @@ pub async fn update_record(record: &RecordUpdate, pool: &PgPool) -> Result<(), U
 
     sqlx::query_unchecked!(
         r#"
-        UPDATE accounting
+        UPDATE auditor
         SET stop_time = $2,
             runtime = $3,
             updated_at = $4
diff --git a/media/website/content/migration.md b/media/website/content/migration.md
index 50f365471..c6cebc014 100644
--- a/media/website/content/migration.md
+++ b/media/website/content/migration.md
@@ -6,6 +6,10 @@ weight = 3
 
 # From 0.5.0 to unreleased/0.6.0
 
+## Auditor DB:
+- WARNING: Please create a backup of the database before running the migration script.
+- AUDITOR db should be migrated to use the new schema. Run `sqlx migrate run --source migration` from the AUDITOR home directory. It is also possible using the container which can be found here [Documentation](../#migrating-the-database)
+
 ## Apel plugin
 
 - The APEL message can now be configured via the config. An updated example config file can be found in the [Documentation](../#apel-plugin).
diff --git a/migrations/20240503141800_convert_meta_component_to_jsonb.sql b/migrations/20240503141800_convert_meta_component_to_jsonb.sql
new file mode 100644
index 000000000..fd4a8abb2
--- /dev/null
+++ b/migrations/20240503141800_convert_meta_component_to_jsonb.sql
@@ -0,0 +1,62 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS auditor (
+    id INT GENERATED ALWAYS AS IDENTITY,
+    record_id TEXT NOT NULL UNIQUE,
+    meta JSONB,
+    components JSONB,
+    start_time TIMESTAMPTZ NOT NULL,
+    stop_time TIMESTAMPTZ,
+    runtime BIGINT,
+    updated_at  TIMESTAMPTZ NOT NULL
+);
+
+INSERT INTO auditor (record_id, meta, components, start_time, stop_time, runtime, updated_at)
+
+SELECT a.record_id,
+                          m.meta::jsonb AS meta,
+                          css.components::jsonb AS Components,
+                          a.start_time,
+                          a.stop_time,
+                          a.runtime,
+                          a.updated_at
+                   FROM accounting a
+                   LEFT JOIN (
+    WITH subquery AS (
+        SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
+        FROM meta as m
+        GROUP BY m.record_id, m.key
+    )               
+    SELECT s.record_id as record_id, jsonb_object_agg(s.key, s.values) as meta
+    FROM subquery as s
+    GROUP BY s.record_id
+) m ON m.record_id = a.record_id
+                   LEFT JOIN (
+                       WITH subquery AS (
+                          SELECT 
+                              c.id as cid,
+                              COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
+                          FROM components as c
+                          LEFT JOIN components_scores as cs
+                          ON c.id = cs.component_id
+                          LEFT JOIN scores as s
+                          ON cs.score_id = s.id
+                          GROUP BY c.id
+                       )
+                       SELECT rc.record_id as id, jsonb_agg(row(c.name, c.amount, sq.scores)::component) as components
+                       FROM records_components AS rc
+                       LEFT JOIN components as c
+                       ON rc.component_id = c.id
+                       LEFT JOIN subquery AS sq
+                       ON sq.cid = rc.component_id
+                       GROUP BY rc.record_id
+                   ) css ON css.id = a.id;
+
+DROP TABLE IF EXISTS records_components;
+DROP TABLE IF EXISTS components_scores;
+DROP TABLE IF EXISTS meta;
+DROP TABLE IF EXISTS scores;
+DROP TABLE IF EXISTS components;
+DROP TABLE IF EXISTS accounting;
+
+COMMIT;


Test patch:
diff --git a/auditor/tests/api/add.rs b/auditor/tests/api/add.rs
index 9c8d86d5f..9f38a9d20 100644
--- a/auditor/tests/api/add.rs
+++ b/auditor/tests/api/add.rs
@@ -1,5 +1,5 @@
 use crate::helpers::spawn_app;
-use auditor::domain::{Component, RecordDatabase, RecordTest};
+use auditor::domain::{RecordDatabase, RecordTest};
 use fake::{Fake, Faker};
 
 #[tokio::test]
@@ -17,45 +17,14 @@ async fn add_returns_a_200_for_valid_json_data() {
 
         let saved = sqlx::query_as!(
             RecordDatabase,
-            r#"SELECT a.record_id,
-                      m.meta as "meta: Vec<(String, Vec<String>)>",
-                      css.components as "components: Vec<Component>",
-                      a.start_time as "start_time?",
-                      a.stop_time,
-                      a.runtime
-               FROM accounting a
-               LEFT JOIN (
-                   WITH subquery AS (
-                       SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                       FROM meta as m
-                       WHERE m.record_id = $1
-                       GROUP BY m.record_id, m.key
-                   )
-                   SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-                   FROM subquery as s
-                   GROUP BY s.record_id
-                   ) m ON m.record_id = a.record_id
-               LEFT JOIN (
-                   WITH subquery AS (
-                      SELECT 
-                          c.id as cid,
-                          COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                      FROM components as c
-                      LEFT JOIN components_scores as cs
-                      ON c.id = cs.component_id
-                      LEFT JOIN scores as s
-                      ON cs.score_id = s.id
-                      GROUP BY c.id
-                   )
-                   SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-                   FROM records_components AS rc
-                   LEFT JOIN components as c
-                   ON rc.component_id = c.id
-                   LEFT JOIN subquery AS sq
-                   ON sq.cid = rc.component_id
-                   GROUP BY rc.record_id
-               ) css ON css.id = a.id
-               WHERE a.record_id = $1
+            r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           WHERE record_id = $1
             "#,
             body.record_id.as_ref().unwrap(),
         )
@@ -93,7 +62,7 @@ async fn add_returns_a_400_for_invalid_json_data() {
 
             assert_eq!(400, response.status().as_u16());
 
-            let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM accounting"#,)
+            let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM auditor"#,)
                 .fetch_all(&app.db_pool)
                 .await
                 .expect("Failed to fetch data");
@@ -138,7 +107,7 @@ async fn add_returns_a_400_when_data_is_missing() {
             "The API did not fail with 400 Bad Request when the payload was {error_message}."
         );
 
-        let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM accounting"#,)
+        let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM auditor"#,)
             .fetch_all(&app.db_pool)
             .await
             .expect("Failed to fetch data");
@@ -174,45 +143,14 @@ async fn bulk_insert_records() {
     for record in records {
         let saved = sqlx::query_as!(
             RecordDatabase,
-            r#"SELECT a.record_id,
-                      m.meta as "meta: Vec<(String, Vec<String>)>",
-                      css.components as "components: Vec<Component>",
-                      a.start_time as "start_time?",
-                      a.stop_time,
-                      a.runtime
-               FROM accounting a
-               LEFT JOIN (
-                   WITH subquery AS (
-                       SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                       FROM meta as m
-                       WHERE m.record_id = $1
-                       GROUP BY m.record_id, m.key
-                   )
-                   SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-                   FROM subquery as s
-                   GROUP BY s.record_id
-                   ) m ON m.record_id = a.record_id
-               LEFT JOIN (
-                   WITH subquery AS (
-                      SELECT 
-                          c.id as cid,
-                          COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                      FROM components as c
-                      LEFT JOIN components_scores as cs
-                      ON c.id = cs.component_id
-                      LEFT JOIN scores as s
-                      ON cs.score_id = s.id
-                      GROUP BY c.id
-                   )
-                   SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-                   FROM records_components AS rc
-                   LEFT JOIN components as c
-                   ON rc.component_id = c.id
-                   LEFT JOIN subquery AS sq
-                   ON sq.cid = rc.component_id
-                   GROUP BY rc.record_id
-               ) css ON css.id = a.id
-               WHERE a.record_id = $1
+            r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           WHERE record_id = $1
             "#,
             record.record_id.as_ref().unwrap(),
         )
@@ -249,7 +187,7 @@ async fn bulk_insert_returns_a_400_for_invalid_json_data() {
 
             assert_eq!(400, response.status().as_u16());
 
-            let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM accounting"#,)
+            let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM auditor"#,)
                 .fetch_all(&app.db_pool)
                 .await
                 .expect("Failed to fetch data");
@@ -305,7 +243,7 @@ async fn bulk_insert_returns_a_400_when_data_is_missing() {
             "The API did not fail with 400 Bad Request when the payload was {error_message}."
         );
 
-        let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM accounting"#,)
+        let saved: Vec<_> = sqlx::query!(r#"SELECT record_id FROM auditor"#,)
             .fetch_all(&app.db_pool)
             .await
             .expect("Failed to fetch data");
diff --git a/auditor/tests/api/client.rs b/auditor/tests/api/client.rs
index 68feb9d7f..972109ebc 100644
--- a/auditor/tests/api/client.rs
+++ b/auditor/tests/api/client.rs
@@ -1,5 +1,5 @@
 use crate::helpers::spawn_app;
-use auditor::domain::{Component, Record, RecordAdd, RecordDatabase, RecordTest, RecordUpdate};
+use auditor::domain::{Record, RecordAdd, RecordDatabase, RecordTest, RecordUpdate};
 use auditor_client::{AuditorClientBuilder, Operator, QueryBuilder};
 use chrono::{TimeZone, Utc};
 use fake::{Fake, Faker};
@@ -28,44 +28,14 @@ async fn add_records() {
 
     let mut saved_records = sqlx::query_as!(
         RecordDatabase,
-        r#"SELECT a.record_id,
-                  m.meta as "meta: Vec<(String, Vec<String>)>",
-                  css.components as "components: Vec<Component>",
-                  a.start_time as "start_time?",
-                  a.stop_time,
-                  a.runtime
-           FROM accounting a
-           LEFT JOIN (
-               WITH subquery AS (
-                   SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                   FROM meta as m
-                   GROUP BY m.record_id, m.key
-               )
-               SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-               FROM subquery as s
-               GROUP BY s.record_id
-               ) m ON m.record_id = a.record_id
-           LEFT JOIN (
-               WITH subquery AS (
-                  SELECT 
-                      c.id as cid,
-                      COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                  FROM components as c
-                  LEFT JOIN components_scores as cs
-                  ON c.id = cs.component_id
-                  LEFT JOIN scores as s
-                  ON cs.score_id = s.id
-                  GROUP BY c.id
-               )
-               SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-               FROM records_components AS rc
-               LEFT JOIN components as c
-               ON rc.component_id = c.id
-               LEFT JOIN subquery AS sq
-               ON sq.cid = rc.component_id
-               GROUP BY rc.record_id
-           ) css ON css.id = a.id
-           ORDER BY a.stop_time
+        r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           ORDER BY stop_time
         "#,
     )
     .fetch_all(&app.db_pool)
@@ -133,44 +103,14 @@ async fn update_records() {
 
     let mut saved_records = sqlx::query_as!(
         RecordDatabase,
-        r#"SELECT a.record_id,
-                  m.meta as "meta: Vec<(String, Vec<String>)>",
-                  css.components as "components: Vec<Component>",
-                  a.start_time as "start_time?",
-                  a.stop_time,
-                  a.runtime
-           FROM accounting a
-           LEFT JOIN (
-               WITH subquery AS (
-                   SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                   FROM meta as m
-                   GROUP BY m.record_id, m.key
-               )
-               SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-               FROM subquery as s
-               GROUP BY s.record_id
-               ) m ON m.record_id = a.record_id
-           LEFT JOIN (
-               WITH subquery AS (
-                  SELECT 
-                      c.id as cid,
-                      COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                  FROM components as c
-                  LEFT JOIN components_scores as cs
-                  ON c.id = cs.component_id
-                  LEFT JOIN scores as s
-                  ON cs.score_id = s.id
-                  GROUP BY c.id
-               )
-               SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-               FROM records_components AS rc
-               LEFT JOIN components as c
-               ON rc.component_id = c.id
-               LEFT JOIN subquery AS sq
-               ON sq.cid = rc.component_id
-               GROUP BY rc.record_id
-           ) css ON css.id = a.id
-           ORDER BY a.stop_time
+        r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           ORDER BY stop_time
         "#,
     )
     .fetch_all(&app.db_pool)
@@ -230,96 +170,26 @@ async fn get_returns_a_list_of_records() {
         let runtime = (record.stop_time.unwrap() - record.start_time.unwrap()).num_seconds();
         let mut transaction = app.db_pool.begin().await.unwrap();
 
-        let id = sqlx::query_unchecked!(
+        sqlx::query_unchecked!(
             r#"
-                INSERT INTO accounting (
-                    record_id, start_time, stop_time, runtime, updated_at
-                )
-                VALUES ($1, $2, $3, $4, $5)
-                RETURNING id;
-            "#,
+        INSERT INTO auditor (
+            record_id, start_time, stop_time, meta, components, runtime, updated_at
+        )
+        VALUES ($1, $2, $3, $4, $5, $6, $7)
+        RETURNING id;
+        "#,
             record.record_id.as_ref(),
             record.start_time,
             record.stop_time,
+            serde_json::to_value(&record.meta).unwrap_or_else(|_| serde_json::Value::Null),
+            serde_json::to_value(&record.components).unwrap_or_else(|_| serde_json::Value::Null),
             runtime,
             Utc::now()
         )
         .fetch_optional(&mut *transaction)
         .await
         .unwrap()
-        .unwrap()
-        .id;
-
-        for component in record.components.as_ref().unwrap().iter() {
-            let (names, scores): (Vec<String>, Vec<f64>) = component
-                .scores
-                .iter()
-                .map(|s| (s.name.as_ref().unwrap().to_string(), s.value.unwrap()))
-                .unzip();
-
-            sqlx::query_unchecked!(
-                r#"
-                WITH insert_components AS (
-                    INSERT INTO components (name, amount)
-                    VALUES ($1, $2)
-                    RETURNING id
-                ),
-                insert_scores AS (
-                    INSERT INTO scores (name, value)
-                    SELECT * FROM UNNEST($3::text[], $4::double precision[])
-                    -- Update if already in table. This isn't great, but 
-                    -- otherwise RETURNING won't return anything.
-                    ON CONFLICT (name, value) DO UPDATE
-                    SET value = EXCLUDED.value, name = EXCLUDED.name
-                    RETURNING id
-                ),
-                insert_components_scores AS (
-                    INSERT INTO components_scores (component_id, score_id)
-                    SELECT (SELECT id FROM insert_components), id
-                    FROM insert_scores
-                )
-                INSERT INTO records_components (record_id, component_id)
-                SELECT $5, (SELECT id from insert_components) 
-                -- FROM accounting WHERE id = $5
-                "#,
-                component.name.as_ref(),
-                component.amount,
-                &names[..],
-                &scores[..],
-                id,
-            )
-            .execute(&mut *transaction)
-            .await
-            .unwrap();
-        }
-
-        if let Some(data) = record.meta.as_ref() {
-            let (rid, names, values): (Vec<String>, Vec<String>, Vec<String>) =
-                itertools::multiunzip(data.iter().flat_map(|(k, v)| {
-                    v.iter()
-                        .map(|v| {
-                            (
-                                record.record_id.as_ref().unwrap().clone(),
-                                k.clone(),
-                                v.clone(),
-                            )
-                        })
-                        .collect::<Vec<_>>()
-                }));
-
-            sqlx::query!(
-                r#"
-                INSERT INTO meta (record_id, key, value)
-                SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])
-                "#,
-                &rid[..],
-                &names[..],
-                &values[..],
-            )
-            .execute(&mut *transaction)
-            .await
-            .unwrap();
-        }
+        .unwrap();
 
         transaction.commit().await.unwrap();
     }
@@ -371,96 +241,26 @@ async fn get_started_since_returns_a_list_of_records() {
 
         let mut transaction = app.db_pool.begin().await.unwrap();
 
-        let id = sqlx::query_unchecked!(
+        sqlx::query_unchecked!(
             r#"
-                INSERT INTO accounting (
-                    record_id, start_time, stop_time, runtime, updated_at
-                )
-                VALUES ($1, $2, $3, $4, $5)
-                RETURNING id;
-            "#,
+        INSERT INTO auditor (
+            record_id, start_time, stop_time, meta, components, runtime, updated_at
+        )
+        VALUES ($1, $2, $3, $4, $5, $6, $7)
+        RETURNING id;
+        "#,
             record.record_id.as_ref(),
             record.start_time,
             record.stop_time,
+            serde_json::to_value(&record.meta).unwrap_or_else(|_| serde_json::Value::Null),
+            serde_json::to_value(&record.components).unwrap_or_else(|_| serde_json::Value::Null),
             runtime,
             Utc::now()
         )
         .fetch_optional(&mut *transaction)
         .await
         .unwrap()
-        .unwrap()
-        .id;
-
-        for component in record.components.as_ref().unwrap().iter() {
-            let (names, scores): (Vec<String>, Vec<f64>) = component
-                .scores
-                .iter()
-                .map(|s| (s.name.as_ref().unwrap().to_string(), s.value.unwrap()))
-                .unzip();
-
-            sqlx::query_unchecked!(
-                r#"
-                WITH insert_components AS (
-                    INSERT INTO components (name, amount)
-                    VALUES ($1, $2)
-                    RETURNING id
-                ),
-                insert_scores AS (
-                    INSERT INTO scores (name, value)
-                    SELECT * FROM UNNEST($3::text[], $4::double precision[])
-                    -- Update if already in table. This isn't great, but 
-                    -- otherwise RETURNING won't return anything.
-                    ON CONFLICT (name, value) DO UPDATE
-                    SET value = EXCLUDED.value, name = EXCLUDED.name
-                    RETURNING id
-                ),
-                insert_components_scores AS (
-                    INSERT INTO components_scores (component_id, score_id)
-                    SELECT (SELECT id FROM insert_components), id
-                    FROM insert_scores
-                )
-                INSERT INTO records_components (record_id, component_id)
-                SELECT $5, (SELECT id from insert_components) 
-                -- FROM accounting WHERE id = $5
-                "#,
-                component.name.as_ref(),
-                component.amount,
-                &names[..],
-                &scores[..],
-                id,
-            )
-            .execute(&mut *transaction)
-            .await
-            .unwrap();
-        }
-
-        if let Some(data) = record.meta.as_ref() {
-            let (rid, names, values): (Vec<String>, Vec<String>, Vec<String>) =
-                itertools::multiunzip(data.iter().flat_map(|(k, v)| {
-                    v.iter()
-                        .map(|v| {
-                            (
-                                record.record_id.as_ref().unwrap().clone(),
-                                k.clone(),
-                                v.clone(),
-                            )
-                        })
-                        .collect::<Vec<_>>()
-                }));
-
-            sqlx::query!(
-                r#"
-                INSERT INTO meta (record_id, key, value)
-                SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])
-                "#,
-                &rid[..],
-                &names[..],
-                &values[..],
-            )
-            .execute(&mut *transaction)
-            .await
-            .unwrap();
-        }
+        .unwrap();
 
         transaction.commit().await.unwrap();
     }
@@ -523,96 +323,26 @@ async fn get_stopped_since_returns_a_list_of_records() {
 
         let mut transaction = app.db_pool.begin().await.unwrap();
 
-        let id = sqlx::query_unchecked!(
+        sqlx::query_unchecked!(
             r#"
-                INSERT INTO accounting (
-                    record_id, start_time, stop_time, runtime, updated_at
-                )
-                VALUES ($1, $2, $3, $4, $5)
-                RETURNING id;
-            "#,
+        INSERT INTO auditor (
+            record_id, start_time, stop_time, meta, components, runtime, updated_at
+        )
+        VALUES ($1, $2, $3, $4, $5, $6, $7)
+        RETURNING id;
+        "#,
             record.record_id.as_ref(),
             record.start_time,
             record.stop_time,
+            serde_json::to_value(&record.meta).unwrap_or_else(|_| serde_json::Value::Null),
+            serde_json::to_value(&record.components).unwrap_or_else(|_| serde_json::Value::Null),
             runtime,
             Utc::now()
         )
         .fetch_optional(&mut *transaction)
         .await
         .unwrap()
-        .unwrap()
-        .id;
-
-        for component in record.components.as_ref().unwrap().iter() {
-            let (names, scores): (Vec<String>, Vec<f64>) = component
-                .scores
-                .iter()
-                .map(|s| (s.name.as_ref().unwrap().to_string(), s.value.unwrap()))
-                .unzip();
-
-            sqlx::query_unchecked!(
-                r#"
-                WITH insert_components AS (
-                    INSERT INTO components (name, amount)
-                    VALUES ($1, $2)
-                    RETURNING id
-                ),
-                insert_scores AS (
-                    INSERT INTO scores (name, value)
-                    SELECT * FROM UNNEST($3::text[], $4::double precision[])
-                    -- Update if already in table. This isn't great, but 
-                    -- otherwise RETURNING won't return anything.
-                    ON CONFLICT (name, value) DO UPDATE
-                    SET value = EXCLUDED.value, name = EXCLUDED.name
-                    RETURNING id
-                ),
-                insert_components_scores AS (
-                    INSERT INTO components_scores (component_id, score_id)
-                    SELECT (SELECT id FROM insert_components), id
-                    FROM insert_scores
-                )
-                INSERT INTO records_components (record_id, component_id)
-                SELECT $5, (SELECT id from insert_components) 
-                -- FROM accounting WHERE id = $5
-                "#,
-                component.name.as_ref(),
-                component.amount,
-                &names[..],
-                &scores[..],
-                id,
-            )
-            .execute(&mut *transaction)
-            .await
-            .unwrap();
-        }
-
-        if let Some(data) = record.meta.as_ref() {
-            let (rid, names, values): (Vec<String>, Vec<String>, Vec<String>) =
-                itertools::multiunzip(data.iter().flat_map(|(k, v)| {
-                    v.iter()
-                        .map(|v| {
-                            (
-                                record.record_id.as_ref().unwrap().clone(),
-                                k.clone(),
-                                v.clone(),
-                            )
-                        })
-                        .collect::<Vec<_>>()
-                }));
-
-            sqlx::query!(
-                r#"
-                INSERT INTO meta (record_id, key, value)
-                SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[])
-                "#,
-                &rid[..],
-                &names[..],
-                &values[..],
-            )
-            .execute(&mut *transaction)
-            .await
-            .unwrap();
-        }
+        .unwrap();
 
         transaction.commit().await.unwrap();
     }
diff --git a/auditor/tests/api/update.rs b/auditor/tests/api/update.rs
index 5bf813bb4..a1ae8af09 100644
--- a/auditor/tests/api/update.rs
+++ b/auditor/tests/api/update.rs
@@ -1,5 +1,5 @@
 use crate::helpers::spawn_app;
-use auditor::domain::{Component, Record, RecordDatabase, RecordTest};
+use auditor::domain::{Record, RecordDatabase, RecordTest};
 use fake::{Fake, Faker};
 
 #[tokio::test]
@@ -59,45 +59,14 @@ async fn update_returns_a_200_for_valid_form_data() {
 
     let saved: Record = sqlx::query_as!(
         RecordDatabase,
-        r#"SELECT a.record_id,
-                  m.meta as "meta: Vec<(String, Vec<String>)>",
-                  css.components as "components: Vec<Component>",
-                  a.start_time as "start_time?",
-                  a.stop_time,
-                  a.runtime
-           FROM accounting a
-           LEFT JOIN (
-               WITH subquery AS (
-                   SELECT m.record_id as record_id, m.key as key, array_agg(m.value) as values
-                   FROM meta as m
-                   WHERE m.record_id = $1
-                   GROUP BY m.record_id, m.key
-               )
-               SELECT s.record_id as record_id, array_agg(row(s.key, s.values)) as meta
-               FROM subquery as s
-               GROUP BY s.record_id
-               ) m ON m.record_id = a.record_id
-           LEFT JOIN (
-               WITH subquery AS (
-                  SELECT 
-                      c.id as cid,
-                      COALESCE(array_agg(row(s.name, s.value)::score) FILTER (WHERE s.name IS NOT NULL AND s.value IS NOT NULL), '{}'::score[]) as scores
-                  FROM components as c
-                  LEFT JOIN components_scores as cs
-                  ON c.id = cs.component_id
-                  LEFT JOIN scores as s
-                  ON cs.score_id = s.id
-                  GROUP BY c.id
-               )
-               SELECT rc.record_id as id, array_agg(row(c.name, c.amount, sq.scores)::component) as components
-               FROM records_components AS rc
-               LEFT JOIN components as c
-               ON rc.component_id = c.id
-               LEFT JOIN subquery AS sq
-               ON sq.cid = rc.component_id
-               GROUP BY rc.record_id
-           ) css ON css.id = a.id
-           WHERE a.record_id = $1
+        r#"SELECT record_id,
+                  meta,
+                  components,
+                  start_time,
+                  stop_time,
+                  runtime
+           FROM auditor
+           WHERE record_id = $1
         "#,
         body.record_id.as_ref().unwrap()
     )