BigQuery — мощный облачный сервис от Google для работы с большими объемами данных. Один из часто используемых случаев использования BigQuery — это интеграция с Google Analytics. Эта связка позволяет компаниям эффективно обрабатывать и анализировать огромные массивы данных о поведении пользователей на сайтах и в приложениях. Благодаря способности обрабатывать большие данные, масштабироваться, делать мгновенные запросы и интегрироваться с другими инструментам BigQuery стал стандартом де-факто для тех, кто хочет перейти от обычных отчетов основанных на табличных данных к гибкой и осмысленной аналитике.
Google решил прекратить работу BigQuery на территории РФ начиная с 9 сентября, и в связи с этим, у многих пользователей возник вопрос относительно дальнейшего сохранения своих исторических данных и их миграции в другое место.
К счастью, существует достаточно понятный способ миграции данных из BigQuery в ClickHouse кластер, который может быть развернут как в Яндекс Облаке, так и в on-prem виде.
В данной статье мы рассмотрим миграцию данных Google Analytics из GCP BigQuery в Yandex Cloud ClickHouse, а также доступ к ним через DataLens, или же WebSQL.
Подготовка ресурсов на GCP
В качестве первого шага, нам необходимо проверить доступ к BigQuery кластеру внутри GCP, а также создать ряд дополнительных ресурсов, которые будут необходимы в процессе миграции.
Создание бакета на GCP
GCP бакет будет использоваться как временное хранилище для выгрузки данных из BigQuery в виде файлов. Для его создания выполните следующие шаги:
В интерфейсе GCP зайдите в Object Storage
Кликните на кнопку “Create”
Введите имя бакета; Для этой статьи мы будем использовать имя
habr-bg-to-ch
Более подробную инструкцию по созданию бакета можно найти в официальном руководстве.
Создание сервисного аккаунта
Сервисный аккаунт будет использоваться для миграции данных из BigQuery в Object Storage, для его создания воспользуйтесь официальным руководством от GCP. Обратите внимание, что у сервисного аккаунта должны быть назначены роли BigQuery Data Editor и Storage Object Admin.
После создания сервисного аккаунта создайте JSON ключ с помощью официальной инструкции и скачайте его в виде файла.
Подготовьте локальный стенд
На машине, где будет запускаться миграция необходимо:
Установить google-cloud-sdk и python-bigquery
Установить gcloud и авторизоваться внутри него с помощью JSON-ключа от созданного на предыдущем шаге сервис аккаунта.
Подготовка ресурсов в Yandex Cloud
Внутри Яндекс облака необходимо выполнить следующие действия:
Создать сервисный аккаунт с ролью storage.uploader для доступа к бакету Object Storage.
Создать статический ключ доступа для сервисного аккаунта, а также сохранить идентификатор ключа и секретный ключ — они будут необходимы далее.
Создать бакет Object Storage с публичным доступом на чтение объектов и к списку объектов в бакете. Как и в случае с GCP, будем использовать имя habr-bg-to-ch.
Теперь нужно подготовить все остальные части инфраструктуры, а именно:
IAM аккаунты,
облачные сети, группы безопасности, сетевые маршруты и NAT Gateway,
jumphost машину,
Lockbox секреты,
ClickHouse кластер с отказоустойчивостью с помощью хостов Zookeeper.
Для создания упомянутых компонентов будем использовать Terraform (или OpenTofu), а также коммьюнити модули для Yandex Cloud: terraform-yacloud-modules.
Полный код Terraform инфраструктуры доступен в репозитории kvendingoldo/habr-bq-to-ch. Сейчас же рассмотрим только основные его части.
01-common.tf
Данный файл содержит в себе создание VPC, групп безопасности, NAT Gateway, и IAM аккаунтов.
Скрытый текст
module "network" {
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-vpc.git?ref=v1.7.0"
blank_name = var.common_name
azs = var.azs
create_nat_gateway = true
public_subnets = var.subnets["public"]
private_subnets = var.subnets["private"]
}
module "security_groups" {
for_each = local.security_groups
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-security-group.git?ref=v1.0.0"
blank_name = format("%s-%s", var.common_name, each.key)
vpc_id = module.network.vpc_id
ingress_rules = each.value["ingress_rules"]
egress_rules = each.value["egress_rules"]
depends_on = [
module.network
]
}
module "iam_accounts" {
for_each = {
for k, v in var.iam : k => v if v["enabled"]
}
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-iam.git//modules/iam-account?ref=v1.0.0"
name = format("%s-%s", var.common_name, each.key)
folder_roles = each.value["folder_roles"]
cloud_roles = each.value["cloud_roles"]
enable_static_access_key = each.value["enable_static_access_key"]
enable_api_key = each.value["enable_api_key"]
enable_account_key = each.value["enable_account_key"]
}
02-vms.tf
Данный файл содержит в себе описание виртуальных машин для Яндекс Облака и LockBox секретов, которые хранят SSH ключи для этих машин.
Скрытый текст
#
# VM instances
#
module "vms" {
for_each = var.vms
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-instance.git?ref=v1.0.0"
name = format("%s-%s", var.common_name, each.key)
zone = each.value["zone"]
subnet_id = local.zone2prvsubnet[each.value["zone"]]
enable_nat = each.value["enable_nat"]
create_pip = each.value["create_pip"]
security_group_ids = [
module.security_groups[each.key].id
]
hostname = each.key
platform_id = each.value["platform_id"]
cores = each.value["cores"]
memory = each.value["memory"]
core_fraction = each.value["core_fraction"]
preemptible = each.value["preemptible"]
image_family = each.value["image_family"]
service_account_id = module.iam_accounts[each.key].id
generate_ssh_key = each.value["generate_ssh_key"]
ssh_user = each.value["ssh_user"]
user_data = null
boot_disk_initialize_params = each.value["boot_disk_initialize_params"]
secondary_disks = each.value["secondary_disks"]
}
#
# Secrets
#
module "vms_secrets" {
for_each = var.vms
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-lockbox.git?ref=v1.0.0"
name = format("%s-%s", var.common_name, each.key)
labels = {}
entries = {
"ssh-prv" : module.vms[each.key].ssh_key_prv
"ssh-pub" : module.vms[each.key].ssh_key_pub
}
deletion_protection = false
}
03-clickhouse.tf
Файл описывает вызов ClickHouse модуля, а также LockBox секреты в которых хранится пароль для администратора ClickHouse кластера.
Скрытый текст
module "clickhouse" {
for_each = local.clickhouse_clusters
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-mdb-clickhouse.git?ref=main"
name = format("%s-%s", var.common_name, each.key)
labels = {}
network_id = module.network.vpc_id
security_group_ids = [module.security_groups[each.key].id]
access = each.value["access"]
users = each.value["users"]
databases = each.value["databases"]
deletion_protection = each.value["deletion_protection"]
clickhouse_disk_size = each.value["clickhouse_disk_size"]
clickhouse_disk_type_id = each.value["clickhouse_disk_type_id"]
clickhouse_resource_preset_id = each.value["clickhouse_resource_preset_id"]
environment = each.value["environment"]
clickhouse_version = each.value["clickhouse_version"]
description = each.value["description"]
sql_user_management = each.value["sql_user_management"]
sql_database_management = each.value["sql_database_management"]
admin_password = each.value["sql_user_management"] ? random_password.clickhouse_admin_password[each.key].result: null
shards = each.value["shards"]
hosts = each.value["hosts"]
cloud_storage = each.value["cloud_storage"]
copy_schema_on_new_hosts = each.value["copy_schema_on_new_hosts"]
backup_window_start = each.value["backup_window_start"]
maintenance_window = {
type = each.value["maintenance_window_type"]
day = each.value["maintenance_window_day"]
hour = each.value["maintenance_window_hour"]
}
depends_on = [module.iam_accounts, module.network]
}
resource "random_password" "clickhouse_admin_password" {
for_each = {
for k, v in local.clickhouse_clusters : k => v if v["sql_user_management"]
}
length = 8
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
module "clickhouse_secrets" {
for_each = {
for k, v in local.clickhouse_clusters : k => v if v["sql_user_management"]
}
source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-lockbox.git?ref=v1.0.0"
name = format("%s-clickhouse-%s", var.common_name, each.key)
labels = {}
entries = {
"admin-password" : random_password.clickhouse_admin_password[each.key].result
}
deletion_protection = false
}
10-variables.tf
В файле находится набор стандартных параметров для нашей инфраструктуры.
Скрытый текст
#
# yandex
#
variable "azs" {
default = ["ru-central1-a", "ru-central1-b", "ru-central1-d"]
}
#
# naming
#
variable "common_name" {
default = "habr-bg-to-ch"
}
#
# network
#
variable "subnets" {
default = {
public = [["10.100.0.0/24"], ["10.101.0.0/24"], ["10.102.0.0/24"]]
private = [["10.103.0.0/24"], ["10.104.0.0/24"], ["10.105.0.0/24"]]
}
}
variable "security_groups" {
default = {
jumphost = {
ingress_rules = {
"ssh" = {
protocol = "tcp"
port = 22
v4_cidr_blocks = ["0.0.0.0/0"]
description = "ssh"
}
}
egress_rules = {
"all" = {
protocol = "any"
from_port = 0
to_port = 65535
v4_cidr_blocks = ["0.0.0.0/0"]
}
}
}
clickhouse-demo = {
ingress_rules = {
"self" = {
protocol = "any"
from_port = 0
to_port = 65535
predefined_target = "self_security_group"
}
"8443_to_internet" = {
protocol = "tcp"
port = 8443
v4_cidr_blocks = ["0.0.0.0/0"]
}
"8123_to_internet" = {
protocol = "tcp"
port = 8123
v4_cidr_blocks = ["0.0.0.0/0"]
}
"9440_to_internet" = {
protocol = "tcp"
port = 9440
v4_cidr_blocks = ["0.0.0.0/0"]
}
"9000_to_internet" = {
protocol = "tcp"
port = 9000
v4_cidr_blocks = ["0.0.0.0/0"]
}
}
egress_rules = {
"all" = {
protocol = "any"
from_port = 0
to_port = 65535
v4_cidr_blocks = ["0.0.0.0/0"]
}
}
}
}
}
#
# IAM
#
variable "iam" {
default = {
jumphost = {
enabled = true
folder_roles = []
cloud_roles = []
enable_static_access_key = false
enable_api_key = false
enable_account_key = false
}
clickhouse-demo = {
enabled = true
folder_roles = []
cloud_roles = []
enable_static_access_key = false
enable_api_key = false
enable_account_key = false
}
}
}
#
# Virtual Machines (VMs)
#
variable "vms" {
default = {
jumphost = {
zone = "ru-central1-a"
cloud_init = {
initial_setup = false
cloud_init_debug = true
}
image_family = "ubuntu-2204-lts"
platform_id = "standard-v2"
cores = 2
memory = 2
core_fraction = 100
preemptible = true
create_pip = true
enable_nat = true
generate_ssh_key = true
ssh_user = "ubuntu"
boot_disk_initialize_params = {
size = 30
type = "network-hdd"
}
secondary_disks = {}
}
}
}
#
# clickhouse
#
variable "clickhouse_clusters" {
default = {
"clickhouse-demo" = {
access = {
data_lens = true
metrika = false
web_sql = true
serverless = false
yandex_query = false
data_transfer = false
}
users = []
databases = []
deletion_protection = false
disk_size = 45
disk_type_id = "network-ssd"
resource_preset_id = "s2.medium"
environment = "PRODUCTION"
version = "23.8"
description = "Demo for Habr: Data export from Google BigQuery"
sql_user_management = true
sql_database_management = true
zookeeper_disk_size = null
zookeeper_disk_type_id = null
zookeeper_resource_preset_id = null
shards = [
{
name = "master01"
weight = 100
resources = {
resource_preset_id = "s2.micro"
disk_size = 5
disk_type_id = "network-ssd"
}
}
]
hosts = [
{
shard_name = "master01"
type = "CLICKHOUSE"
zone = "ru-central1-a"
assign_public_ip = false
},
{
shard_name = "zk01"
type = "ZOOKEEPER"
zone = "ru-central1-a"
assign_public_ip = false
},
{
shard_name = "zk02"
type = "ZOOKEEPER"
zone = "ru-central1-a"
assign_public_ip = false
},
{
shard_name = "zk03"
type = "ZOOKEEPER"
zone = "ru-central1-a"
assign_public_ip = false
}
]
cloud_storage = {
enabled = false
move_factor = 0
data_cache_enabled = true
data_cache_max_size = 0
}
copy_schema_on_new_hosts = true
backup_window_start = {
hours = "12"
minutes = 00
}
maintenance_window_type = "WEEKLY"
maintenance_window_hour = 1
maintenance_window_day = "SUN"
}
}
}
Деплой Terraform инфраструктуры
Для применения вышеописанной Terraform инфраструктуры нужно выполнить несколько простых шагов:
Создать файл конфигурацией зеркала от Яндекса для скачивания Terraform плагинов. К сожалению, данное действие необходимо из-за блокировки официальных провайдер-реестров со стороны Hashicorp и OpenTofu.
Вариант для Terraform (файл ~/.terraformrc
):
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
Вариант для OpenTofu (файл ~/.tofurc
):
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.opentofu.org/*/*"]
}
direct {
exclude = ["registry.opentofu.org/*/*"]
}
}
Скачать и установить Terraform (или OpenTofu). Сделать это можно например с помощью утилиты tenv - менеджера версий для OpenTofu / Terraform / Terragrunt и Atmos.
Установить и настроить yc-cli с помощью официальной инструкции от Яндекса.
Подготовить S3 backend для сохранения Terraform state внутри Яндекс облака.
Экспортировать необходимые для Yandex Provider окружения среды.
export YC_TOKEN=<...>
export YC_CLOUD_ID=<...>
export YC_FOLDER_ID=<...>
export AWS_ACCESS_KEY_ID=<...>
export AWS_SECRET_ACCESS_KEY=<...>
Выполнить последовательно команды:
$ tenv tf install latest
$ terraform init
$ terraform plan
$ terraform apply
После выполнения всех этих действий у вас будет готовая инфраструктура в Яндекс Облаке, которая состоит из настроенного кластера ClickHouse находящегося в закрытой сети, без публичных IP-адресов. Чтобы получить к нему доступ, необходимо использовать также созданный jumphost, и через него прокинуть себе локально порты. SSH ключи для jumphost можно найти в соответствующем ему lockbox секрете.
Перенос данных из GCP BigQuery в Yandex Object Storage
На данном этапе нам необходимо перенести «сырые» данные в формате Apache Parquet из BigQuery в бакет внутри Яндекс облака.
Для этого сначала необходимо создать файл credentials.boto
с параметрами доступа к ресурсам Google Cloud и Yandex Cloud:
[Credentials]
gs_service_client_id = <сервисный_аккаунт_google_cloud>
gs_service_key_file = <абсолютный_путь_к_json_файлу>
aws_access_key_id = <идентификатор_ключа_сервисного_аккаунта>
aws_secret_access_key = <секретный_ключ_сервисного_аккаунта>
[GSUtil]
default_project_id = <идентификатор_проекта_Google_Cloud>
[s3]
calling_format = boto.s3.connection.OrdinaryCallingFormat
host = storage.yandexcloud.net
Где:
gs_service_client_id
— имя сервисного аккаунта Google Cloud в формате service-account-name@project-id.iam.gserviceaccount.com.gs_service_key_file
— абсолютный путь к JSON-файлу ключа доступа сервисного аккаунта Google Cloud.aws_access_key_id
— идентификатор ключа сервисного аккаунта Yandex Cloud.aws_secret_access_key
— секретный ключ сервисного аккаунта Yandex Cloud.default_project_id
— идентификатор проекта Google Cloud.
После подготовки файла credentials.boto
необходимо выполнить скрипт 00_migrate.py:
export GOOGLE_APPLICATION_CREDENTIALS="<path_to_json>"
export BOTO_CONFIG="./credentials.boto"
python3 00_migrate.py \
--bq_project=<bq_project_id> \
--bq_location=<bq_location> \
--gs_bucket=habr-bg-to-ch \
--yc_bucket=habr-bg-to-ch
По окончанию работы скрипта, в S3 бакете внутри вашего Яндекс Облака вы увидите данные из BigQuery в формате Parquet. Обратите внимание, что синхронизация GCP и Yandex Cloud бакетов происходит через rsync, а следовательно это может занять какое-то время.
Использование смигрированных данных
Начиная с этого момента данные уже можно использовать с помощью табличной функции s3Cluster и запросов следующего вида:
SELECT *
FROM s3Cluster(
'<идентификатор_кластера>',
'https://storage.yandexcloud.net/<имя_бакета_Object_Storage>/events-*',
'Parquet',
<поля>
)
Если в ваших таблицах не очень много данных, то такой подход будет неплохо работать, но не в случае с большой аналитикой. Во время выполнения таких запросов, данные выкачиваются в память ClickHouse кластера с медленного S3 хранилища, что во-первых, медленно, а во-вторых, может не очень стабильно работать из-за нехватки оперативной памяти на ClickHouse нодах.
Чтобы ускорить запросы, а также избежать падения ClickHouse узлов все смигрированные на S3 данные нужно экспортировать внутрь ClickHouse в естественном виде. Для небольшого объема таблиц с предсказуемыми полями это не сложно, но в случае Google Analytics это может вызывать некоторые трудности из-за большой вложенности полей и отличающихся между собой схем данных.
Чтобы упросить процесс импорта, в уже упомянутом репозитории kvendingoldo/habr-bq-to-ch есть скрипт 01_import.py работающий по следующему алгоритму.
Забрать список всех таблиц из BigQuery базы.
Пройтись по каждой таблице, и на основании ее схемы, создать SQL схему данных для ClickHouse.
На основании полученной схемы данных для ClickHouse® создать таблицу с помощью SQL.
Импортировать данные из файла S3 соответствующего названию таблицы.
Для запуска этого скрипта в случае нашей инфраструктуры созданной через Terraform необходимо предварительно прокинуть порт для ClickHouse локально, так как кластер находится в приватной сети и не доступен из Интернета. Сделать это можно следующей команды: $ ssh -L 127.0.0.1:8123:<clickhouse_cluster_id>.mdb.yandexcloud.net:8123 ubuntu@<jumphost_ip> -v
После прокидывания портов можно запустить миграцию данных следующей командой:
export GOOGLE_APPLICATION_CREDENTIALS="<path_to_json>"
export BOTO_CONFIG="./credentials.boto"
python3 01_import.py \
--s3_bucket_name=habr-bg-to-ch \
--s3_access_key='<yc_bucket_access_key>' \
--s3_secret_key='<yc_bucket_secret_key>' \
--ch_cluster_id='<clickhouse_cluster_id>' \
--ch_host='clickhouse_cluster_host' \
--ch_password='<clickhouse_cluster_password>' \
--ch_database='clickhouse_database' \
--bq_table_pattern='<tables_prefix_to_import>' \
--bq_project_id="<bq_project_id>" \
--bq_database="<bq_database>"
После окончания работы скрипта, данные будут находится внутри ClickHouse, а доступ к ним можно будет получить с помощью обычных SQL запросов через WebSQL, DataLens, CLI и тд. Подробнее про доступ к данным через WebSQL можно прочитать в официальной документации Яндекс Облака.
Удаление временных ресурсов
После завершения миграции данных можно удалить все ресурсы, которые уже больше не актуальны:
GCP сервис аккаунт
GCP S3 bucket
habr-bg-to-ch
Yandex S3 bucket
habr-bg-to-ch
Выводы
Миграция данных из BigQuery в ClickHouse в целом не сложный процесс. Самая большая проблема в ней — конвертация схемы данных. Следуя описанным в статье шагам можно смигрировать данные не только в Яндекс Облако, но и в ваш локальный ClickHouse кластер. Так же, тот же S3 bucket внутри Яндекс Облака можно легко заменить на minio или любое другой S3-совместимое хранилище, доступное из ClickHouse кластера.
Миграция данных из BigQuery в ClickHouse в целом не представляет значительной сложности. Основная сложность в этом процессе — это корректная конвертация схемы данных. Следуя шагам, описанным в статье, вы сможете успешно перенести данные как в Яндекс Облако, так и в ваш локальный ClickHouse кластер. Более того, используемый в примере S3 bucket в Яндекс Облаке можно легко заменить на MinIO или любое другое S3-совместимое хранилище, доступное вашему ClickHouse кластеру, например Selectel S3. Такой подход обеспечит гибкость и позволит адаптировать процесс миграции под конкретные требования вашей инфраструктуры, что сделает его еще более универсальным и масштабируемым.
Желаю вам успешных миграций!