Skip to content

Instantly share code, notes, and snippets.

@qnkhuat
Last active August 1, 2024 11:09
Show Gist options
  • Save qnkhuat/23552c5defc9db007de83f463a85e681 to your computer and use it in GitHub Desktop.
Save qnkhuat/23552c5defc9db007de83f463a85e681 to your computer and use it in GitHub Desktop.
(ns stress-big-dashboard
(:require
[clojure.java.shell :as sh]
[metabase.sync :as sync]
[metabase.test :as mt]
[toucan2.core :as t2]
[toucan2.tools.with-temp :as t2.with-temp]))
"""
##
- AppDBs: Postgres and MySQL
- Data Warehouses: Postgres, BigQuery, Snowflake
- Dashboard with many dashcards based on models and filters
- 100 tables
- with 100 columns each, of many types
- and with 100 rows of random data each
- 100 MBQL models, one for each table, that returns the whole thing
- 100 MBQL queries, one for each model, that does `COUNT`
- 1 dashboard
- with 19 filters (9 int category fields, 10 string category fields)
- with all 100 `COUNT` queries in one tab
- all wired to all 19 filters
- Sandboxing: users who are sandboxed and users who are not
"""
#_(def create-db-sql
"
DO
$$
DECLARE
table_idx INT;
column_idx INT;
col_type_idx INT;
col_types TEXT[] := ARRAY['INT', 'TIMESTAMP', 'FLOAT', 'TEXT'];
col_type TEXT;
insert_stmt TEXT;
BEGIN
FOR table_idx IN 1..100 LOOP
EXECUTE format('CREATE TABLE table_%s (', table_idx) ||
array_to_string(ARRAY(
SELECT format('col_%s_%s %s', table_idx, col_idx, col_types[(col_idx % 4) + 1])
FROM generate_series(1, 100) AS col_idx
), ', ') ||
');';
insert_stmt := '';
FOR column_idx IN 1..100 LOOP
col_type_idx := (column_idx % 4) + 1;
col_type := col_types[col_type_idx];
IF col_type = 'INT' THEN
insert_stmt := insert_stmt || format('%s', floor(random())::int) || ', ';
ELSIF col_type = 'TIMESTAMP' THEN
insert_stmt := insert_stmt || 'current_timestamp, ';
ELSIF col_type = 'FLOAT' THEN
insert_stmt := insert_stmt || 'random(), ';
ELSE
insert_stmt := insert_stmt || format('''sample_text_%s''', column_idx) || ', ';
END IF;
END LOOP;
insert_stmt := left(insert_stmt, -2); -- Remove last comma and space
FOR row_idx IN 1..100 LOOP
EXECUTE format('INSERT INTO table_%s VALUES (%s);', table_idx, insert_stmt);
END LOOP;
END LOOP;
END
$$;
")
(defn create-db!
[db-name]
(let [db (t2/insert-returning-instance!
:model/Database
(merge
(t2.with-temp/with-temp-defaults :model/Database)
{:name db-name
:engine :postgres
:details {:host "localhost"
:port 5432
:dbname db-name
:user "postgres"}}))]
(sync/sync-database! db)
db))
(defn create-cards
"Given a db, create a model for each table of the DB.
Then for each model, create a question based on that model with :count aggregation."
[db-id]
(let [tables (t2/select :model/Table :db_id db-id)
models (t2/insert-returning-instances! :model/Card (for [table tables]
(merge
(t2.with-temp/with-temp-defaults :model/Card)
{:table_id (:id table)
:type "model"
:name (format "Model with table %s" (:name table))
:dataset_query {:database db-id
:type :query
:query {:source-table (:id table)}}})))]
(t2/insert-returning-instances! :model/Card (for [model models]
(merge
(t2.with-temp/with-temp-defaults :model/Card)
{:table_id (:table_id model)
:type "question"
:name (format "Question with source is card %d" (:id model))
:dataset_query {:database db-id
:type :query
:aggregation [[:count]]
:query {:source-table (format "card__%d" (:id model))}}})))))
(defn create-dashboard-cards
[dashboard-id cards]
(t2/insert-returning-instances! :model/DashboardCard (for [card cards]
(merge
(t2.with-temp/with-temp-defaults :model/DashboardCard)
{:dashboard_id dashboard-id
:card_id (:id card)}))))
(defn create-filters!
[dashcards n-text-filter n-number-fitler]
(let [text-params (for [i (range n-text-filter)]
{:name (format "text filter %d" i)
:slug (format "text_filter_%d" i)
:id (subs (str (random-uuid)) 0 7)
:type "string/="
:sectionId "string"})
number-params (for [i (range n-number-fitler)]
{:name (format "number filter %d" i)
:slug (format "number_filter_%d" i)
:id (subs (str (random-uuid)) 0 7)
:type "number/="
:sectionId "number"})]
(t2/update! :model/Dashboard (:dashboard_id (first dashcards))
{:parameters (concat text-params number-params)})
(doseq [dashcard dashcards]
(let [card (t2/select-one :model/Card (:card_id dashcard))
fields (t2/select :model/Field :table_id (:table_id card))
text-fields (filter #(= (:base_type %) :type/Text) fields)
number-fields (filter #(= (:base_type %) :type/Integer) fields)]
(t2/update! :model/DashboardCard
(:id dashcard)
{:parameter_mappings (concat
(for [[i text-param] (map-indexed vector text-params)]
{:parameter_id (:id text-param)
:card_id (:card_id dashcard)
:target [:dimension [:field (:name (nth text-fields i)) {:base-type :type/Text}]]})
(for [[i number-param] (map-indexed vector number-params)]
{:parameter_id (:id number-param)
:card_id (:card_id dashcard)
:target [:dimension [:field (:name (nth number-fields i)) {:base-type :type/Integer}]]}))})))))
(defn make-big-dashboard
[db-id]
(let [dashboard-id (t2/insert-returning-pk! :model/Dashboard (t2.with-temp/with-temp-defaults :model/Dashboard))
cards (create-cards db-id)
dashcards (create-dashboard-cards dashboard-id cards)]
(create-filters! dashcards 10 9)
dashboard-id))
(def db (create-db! "big_tables"))
(def dashboard-id (make-big-dashboard (:id db)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment