split-silo-database 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. #!/usr/bin/env python
  2. from typing import List
  3. import click
  4. from django.apps import apps
  5. import docker
  6. from sentry.runner import configure
  7. from sentry.silo.base import SiloMode
  8. configure()
  9. def exec_run(container, command):
  10. wrapped_command = f'sh -c "{" ".join(command)}"'
  11. exit_code, output = container.exec_run(cmd=wrapped_command, stdout=True, stderr=True)
  12. if exit_code:
  13. click.echo("Container operation Failed!")
  14. click.echo(f"Container operation failed with {output}")
  15. def split_database(tables: List[str], source: str, destination: str, reset: bool, verbose: bool):
  16. click.echo(f">> Dumping tables from {source} database")
  17. command = ["pg_dump", "-U", "postgres", "-d", source, "--clean"]
  18. for table in tables:
  19. command.extend(["-t", table])
  20. command.extend([">", f"/tmp/{destination}-tables.sql"])
  21. client = docker.from_env()
  22. postgres = client.containers.get("sentry_postgres")
  23. if verbose:
  24. click.echo(f">> Running {' '.join(command)}")
  25. exec_run(postgres, command)
  26. if reset:
  27. click.echo(f">> Dropping existing {destination} database")
  28. exec_run(postgres, ["dropdb", "-U", "postgres", "--if-exists", destination])
  29. exec_run(postgres, ["createdb", "-U", "postgres", destination])
  30. # Use the dump file to build control silo tables.
  31. click.echo(f">> Building {destination} database from dump file")
  32. import_command = ["psql", "-U", "postgres", destination, "<", f"/tmp/{destination}-tables.sql"]
  33. if verbose:
  34. click.echo(f">> Running {' '.join(import_command)}")
  35. exec_run(postgres, import_command)
  36. @click.command()
  37. @click.option("--verbose", default=False, is_flag=True, help="Enable verbose logging")
  38. @click.option(
  39. "--reset",
  40. default=False,
  41. is_flag=True,
  42. help="Reset the target databases to be empty before loading extracted data and schema.",
  43. )
  44. @click.option("--database", default="sentry", help="Which database to derive splits from")
  45. def main(database: str, reset: bool, verbose: bool):
  46. """
  47. This is a development tool that can convert a monolith database into
  48. control + region databases by using silo annotations.
  49. This operation will not modify the original source database.
  50. """
  51. region_tables = ["django_migrations"]
  52. control_tables = ["django_migrations"]
  53. for model in apps.get_models():
  54. silo_limit = getattr(model._meta, "silo_limit", None)
  55. if not silo_limit:
  56. click.echo(f"> Could not find silo assignment for {model._meta.db_table}")
  57. continue
  58. if SiloMode.CONTROL in silo_limit.modes:
  59. control_tables.append(model._meta.db_table)
  60. if SiloMode.REGION in silo_limit.modes:
  61. region_tables.append(model._meta.db_table)
  62. split_database(control_tables, database, "control", reset=reset, verbose=verbose)
  63. split_database(region_tables, database, "region", reset=reset, verbose=verbose)
  64. if __name__ == "__main__":
  65. main()