Ctrl K
ComunidadGuías…

scripts/post_migrate_clickhouse.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
require("dotenv").config();

const { createClient } = require("@clickhouse/client");

async function main() {
  const url = process.env.CLICKHOUSE_URL;
  const adminUser = process.env.CLICKHOUSE_USER;
  const adminPassword = process.env.CLICKHOUSE_PASSWORD;
  const workerUser = process.env.WORKER_CLICKHOUSE_WRITE_USER;
  const workerPassword = process.env.WORKER_CLICKHOUSE_WRITE_PASSWORD;

  if (!url || !adminUser || !adminPassword) {
    console.error(
      "Post-migration (clickhouse): CLICKHOUSE_URL, CLICKHOUSE_USER, and CLICKHOUSE_PASSWORD must be set.",
    );
    process.exit(1);
  }

  const client = createClient({
    url,
    username: adminUser,
    password: adminPassword,
  });

  try {
    await client.command({
      query: `GRANT dictGet ON analytics.* TO backend_role`,
    });
    await client.command({
      query: `GRANT SELECT ON system.dictionaries TO backend_role`,
    });
    await client.command({
      query: `GRANT SYSTEM RELOAD DICTIONARY ON *.* TO backend_role`,
    });
    await client.command({
      query: `GRANT dictGet ON analytics.* TO dashboard_role`,
    });

    const parseDays = (value) => {
      const days = parseInt(value ?? "", 10);
      return Number.isFinite(days) ? days : undefined;
    };
    const retentionDays = parseDays(process.env.REPLAY_RETENTION_DAYS) ?? 60;
    const replayTables = ["session_replays", "session_replay_segments"];
    const retentionMarker = `replay_retention_days=${retentionDays > 0 ? retentionDays : -1}`;
    for (const table of replayTables) {
      const result = await client.query({
        query: `SELECT comment, engine_full FROM system.tables WHERE database = 'analytics' AND name = '${table}'`,
        format: "JSONEachRow",
      });
      const rows = await result.json();
      if (rows[0]?.comment === retentionMarker) continue;

      const hasTtl = /\bTTL\b/.test(rows[0]?.engine_full ?? "");
      if (retentionDays > 0) {
        await client.command({
          query: `ALTER TABLE analytics.${table} MODIFY TTL date + INTERVAL ${retentionDays} DAY DELETE`,
        });
      } else if (hasTtl) {
        await client.command({ query: `ALTER TABLE analytics.${table} REMOVE TTL` });
      }
      await client.command({
        query: `ALTER TABLE analytics.${table} MODIFY COMMENT '${retentionMarker}'`,
      });
      console.log(
        `Post-migration (clickhouse): set analytics.${table} replay retention to ${retentionDays > 0 ? `${retentionDays} days` : "indefinite"}.`,
      );
    }

    // Retention is per dashboard (retention-purge job); drop the TTL older backends set from their
    // retention env var. Checked first: REMOVE TTL on a table without one throws BAD_ARGUMENTS.
    const eventsResult = await client.query({
      query: `SELECT engine_full FROM system.tables WHERE database = 'analytics' AND name = 'events'`,
      format: "JSONEachRow",
    });
    const [eventsTable] = await eventsResult.json();
    if (/\bTTL\b/.test(eventsTable?.engine_full ?? "")) {
      await client.command({ query: `ALTER TABLE analytics.events REMOVE TTL` });
      console.log("Post-migration (clickhouse): removed table TTL from analytics.events.");
    }

    if (!workerUser || !workerPassword) {
      console.log(
        "Post-migration (clickhouse): WORKER_CLICKHOUSE_WRITE_USER not set, skipping worker user creation.",
      );
      return;
    }

    await client.command({
      query: `CREATE USER IF NOT EXISTS ${workerUser} IDENTIFIED BY '${workerPassword.replace(/'/g, "\\'")}'`,
    });
    await client.command({
      query: `ALTER USER ${workerUser} IDENTIFIED BY '${workerPassword.replace(/'/g, "\\'")}'`,
    });
    await client.command({ query: `CREATE ROLE IF NOT EXISTS worker_role` });
    await client.command({
      query: `GRANT SELECT ON analytics.* TO worker_role`,
    });
    await client.command({
      query: `GRANT dictGet ON analytics.* TO worker_role`,
    });
    await client.command({
      query: `GRANT DELETE ON analytics.* TO worker_role`,
    });
    await client.command({
      query: `GRANT ALTER UPDATE ON analytics.* TO worker_role`,
    });
    await client.command({
      query: `GRANT ALTER DELETE ON analytics.* TO worker_role`,
    });
    await client.command({ query: `GRANT worker_role TO ${workerUser}` });

    console.log(
      "Post-migration (clickhouse): user and privileges ensured successfully.",
    );
  } catch (error) {
    console.error(
      "Post-migration (clickhouse): Failed:",
      error instanceof Error ? error.message : String(error),
    );
    process.exit(1);
  } finally {
    await client.close();
  }
}

main();