diff --git a/lib/mix/tasks/clean_clickhouse.ex b/lib/mix/tasks/clean_clickhouse.ex index 2430272e494e..b7917c9d47a0 100644 --- a/lib/mix/tasks/clean_clickhouse.ex +++ b/lib/mix/tasks/clean_clickhouse.ex @@ -11,6 +11,8 @@ defmodule Mix.Tasks.CleanClickhouse do tables -- [ "schema_migrations", + "failed_batches", + "failed_batches_dict", "location_data", "location_data_dict", "acquisition_channel_source_category", diff --git a/lib/plausible/data_migration/failed_batches.ex b/lib/plausible/data_migration/failed_batches.ex new file mode 100644 index 000000000000..fe5c9eaac1c4 --- /dev/null +++ b/lib/plausible/data_migration/failed_batches.ex @@ -0,0 +1,23 @@ +defmodule Plausible.DataMigration.FailedBatches do + @moduledoc """ + ClickHouse failed batches data migration for storing failed batches in ClickHouse. + """ + + use Plausible.DataMigration, dir: "FailedBatches", repo: Plausible.IngestRepo + + def run do + cluster? = Plausible.IngestRepo.clustered_table?("sessions_v2") + + {:ok, _} = + run_sql("create-failed-batches-table", + cluster?: cluster?, + table_settings: Plausible.MigrationUtils.table_settings_expr(:prefix) + ) + + {:ok, _} = + run_sql("create-failed-batches-dictionary", + cluster?: cluster?, + dictionary_connection_params: Plausible.MigrationUtils.dictionary_connection_params() + ) + end +end diff --git a/priv/data_migrations/FailedBatches/sql/create-failed-batches-dictionary.sql.eex b/priv/data_migrations/FailedBatches/sql/create-failed-batches-dictionary.sql.eex new file mode 100644 index 000000000000..3e3ffc41c305 --- /dev/null +++ b/priv/data_migrations/FailedBatches/sql/create-failed-batches-dictionary.sql.eex @@ -0,0 +1,9 @@ +CREATE OR REPLACE DICTIONARY failed_batches_dict +<%= if @cluster? do %>ON CLUSTER '{cluster}'<% end %> +( + `batch` UInt64 +) +PRIMARY KEY batch +SOURCE(CLICKHOUSE(TABLE failed_batches <%= @dictionary_connection_params %> invalidate_query 'SELECT max(batch) FROM failed_batches')) +LIFETIME(MIN 300 MAX 360) +LAYOUT(hashed()) diff --git a/priv/data_migrations/FailedBatches/sql/create-failed-batches-table.sql.eex b/priv/data_migrations/FailedBatches/sql/create-failed-batches-table.sql.eex new file mode 100644 index 000000000000..708b01a07f66 --- /dev/null +++ b/priv/data_migrations/FailedBatches/sql/create-failed-batches-table.sql.eex @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS failed_batches <%= if @cluster? do %>ON CLUSTER '{cluster}'<% end %> +( + `batch` UInt64 +) +<%= if @cluster? do %> +ENGINE = ReplicatedMergeTree('/clickhouse/{cluster}/tables/{shard}/plausible_prod/failed_batches', '{replica}') +<% else %> +ENGINE = MergeTree() +<% end %> +ORDER BY (batch) +<%= @table_settings %> diff --git a/priv/ingest_repo/migrations/20250911084719_add_batch_to_sessions_and_events.exs b/priv/ingest_repo/migrations/20250911084719_add_batch_to_sessions_and_events.exs new file mode 100644 index 000000000000..e1edcd19bf03 --- /dev/null +++ b/priv/ingest_repo/migrations/20250911084719_add_batch_to_sessions_and_events.exs @@ -0,0 +1,70 @@ +defmodule Plausible.IngestRepo.Migrations.AddBatchToSessionsAndEvents do + use Ecto.Migration + + @on_cluster_sessions Plausible.MigrationUtils.on_cluster_statement("sessions_v2") + @on_cluster_events Plausible.MigrationUtils.on_cluster_statement("events_v2") + + def up do + execute """ + ALTER TABLE sessions_v2 + #{@on_cluster_sessions} + ADD COLUMN batch UInt64 + """ + + execute """ + ALTER TABLE sessions_v2 + #{@on_cluster_sessions} + ADD INDEX IF NOT EXISTS minmax_batch batch + TYPE minmax GRANULARITY 1 + """ + + execute """ + ALTER TABLE sessions_v2 + MATERIALIZE INDEX minmax_batch + """ + + execute """ + ALTER TABLE events_v2 + #{@on_cluster_events} + ADD COLUMN batch UInt64 + """ + + execute """ + ALTER TABLE events_v2 + #{@on_cluster_events} + ADD INDEX IF NOT EXISTS minmax_batch batch + TYPE minmax GRANULARITY 1 + """ + + execute """ + ALTER TABLE events_v2 + MATERIALIZE INDEX minmax_batch + """ + end + + def down do + execute """ + ALTER TABLE sessions_v2 + #{@on_cluster_sessions} + DROP INDEX IF EXISTS minmax_batch + """ + + execute """ + ALTER TABLE sessions_v2 + #{@on_cluster_sessions} + DROP COLUMN IF EXISTS batch + """ + + execute """ + ALTER TABLE events_v2 + #{@on_cluster_events} + DROP INDEX IF EXISTS minmax_batch + """ + + execute """ + ALTER TABLE events_v2 + #{@on_cluster_events} + DROP COLUMN IF EXISTS batch + """ + end +end diff --git a/priv/ingest_repo/migrations/20250912094034_add_failed_batches_table_dict.exs b/priv/ingest_repo/migrations/20250912094034_add_failed_batches_table_dict.exs new file mode 100644 index 000000000000..11ba53c8a987 --- /dev/null +++ b/priv/ingest_repo/migrations/20250912094034_add_failed_batches_table_dict.exs @@ -0,0 +1,11 @@ +defmodule Plausible.IngestRepo.Migrations.AddFailedBatchesTableDict do + use Ecto.Migration + + def up do + Plausible.DataMigration.FailedBatches.run() + end + + def down do + raise "Irreversible" + end +end diff --git a/priv/ingest_repo/structure.sql b/priv/ingest_repo/structure.sql index ba41d631b49f..01569cb87f7b 100644 --- a/priv/ingest_repo/structure.sql +++ b/priv/ingest_repo/structure.sql @@ -1,3 +1,46 @@ +CREATE TABLE plausible_events_db.sessions_v2_tmp_versioned +( + `session_id` UInt64, + `sign` Int8, + `site_id` UInt64, + `user_id` UInt64, + `hostname` String CODEC(ZSTD(3)), + `timestamp` DateTime CODEC(Delta(4), LZ4), + `start` DateTime CODEC(Delta(4), LZ4), + `is_bounce` UInt8, + `entry_page` String CODEC(ZSTD(3)), + `exit_page` String CODEC(ZSTD(3)), + `pageviews` Int32, + `events` Int32, + `duration` UInt32, + `referrer` String CODEC(ZSTD(3)), + `referrer_source` String CODEC(ZSTD(3)), + `country_code` LowCardinality(FixedString(2)), + `screen_size` LowCardinality(String), + `operating_system` LowCardinality(String), + `browser` LowCardinality(String), + `utm_medium` String CODEC(ZSTD(3)), + `utm_source` String CODEC(ZSTD(3)), + `utm_campaign` String CODEC(ZSTD(3)), + `browser_version` LowCardinality(String), + `operating_system_version` LowCardinality(String), + `subdivision1_code` LowCardinality(String), + `subdivision2_code` LowCardinality(String), + `city_geoname_id` UInt32, + `utm_content` String CODEC(ZSTD(3)), + `utm_term` String CODEC(ZSTD(3)), + `transferred_from` String, + `entry_meta.key` Array(String) CODEC(ZSTD(3)), + `entry_meta.value` Array(String) CODEC(ZSTD(3)), + INDEX minmax_timestamp timestamp TYPE minmax GRANULARITY 1 +) +ENGINE = CollapsingMergeTree(sign) +PARTITION BY toYYYYMM(start) +PRIMARY KEY (site_id, toDate(start), user_id, session_id) +ORDER BY (site_id, toDate(start), user_id, session_id) +SAMPLE BY user_id +SETTINGS index_granularity = 8192; + CREATE TABLE plausible_events_db.sessions_v2 ( `session_id` UInt64, @@ -46,7 +89,11 @@ CREATE TABLE plausible_events_db.sessions_v2 `region_name` String ALIAS dictGet('plausible_events_db.location_data_dict', 'name', ('subdivision', subdivision1_code)), `city_name` String ALIAS dictGet('plausible_events_db.location_data_dict', 'name', ('city', city_geoname_id)), `channel` LowCardinality(String), - INDEX minmax_timestamp timestamp TYPE minmax GRANULARITY 1 + `click_id_param` LowCardinality(String), + `acquisition_channel` LowCardinality(String) MATERIALIZED multiIf(position(lower(utm_campaign), 'cross-network') > 0, 'Cross-network', lower(utm_medium) IN ('display', 'banner', 'expandable', 'interstitial', 'cpm'), 'Display', match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') AND ((dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SHOPPING') OR match(lower(utm_campaign), '^(.*(([^a-df-z]|^)shop|shopping).*)$')), 'Paid Shopping', ((dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SEARCH') AND (match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') OR dictHas('plausible_events_db.acquisition_channel_paid_sources_dict', lower(utm_source)))) OR ((lower(referrer_source) = 'google') AND (click_id_param = 'gclid')) OR ((lower(referrer_source) = 'bing') AND (click_id_param = 'msclkid')), 'Paid Search', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SOCIAL') AND (match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') OR dictHas('plausible_events_db.acquisition_channel_paid_sources_dict', lower(utm_source))), 'Paid Social', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_VIDEO') AND (match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') OR dictHas('plausible_events_db.acquisition_channel_paid_sources_dict', lower(utm_source))), 'Paid Video', match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$'), 'Paid Other', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SHOPPING') OR match(lower(utm_campaign), '^(.*(([^a-df-z]|^)shop|shopping).*)$'), 'Organic Shopping', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SOCIAL') OR (lower(utm_medium) IN ('social', 'social-network', 'social-media', 'sm', 'social network', 'social media')), 'Organic Social', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_VIDEO') OR (position(lower(utm_medium), 'video') > 0), 'Organic Video', dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SEARCH', 'Organic Search', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_EMAIL') OR match(lower(utm_source), 'e[-_ ]?mail|newsletter') OR match(lower(utm_medium), 'e[-_ ]?mail|newsletter'), 'Email', lower(utm_medium) = 'affiliate', 'Affiliates', lower(utm_medium) = 'audio', 'Audio', lower(utm_source) = 'sms', 'SMS', lower(utm_medium) = 'sms', 'SMS', endsWith(lower(utm_medium), 'push') OR multiSearchAny(lower(utm_medium), ['mobile', 'notification']) OR (lower(referrer_source) = 'firebase'), 'Mobile Push Notifications', (lower(utm_medium) IN ('referral', 'app', 'link')) OR (NOT empty(lower(referrer_source))), 'Referral', 'Direct'), + `batch` UInt64, + INDEX minmax_timestamp timestamp TYPE minmax GRANULARITY 1, + INDEX minmax_batch batch TYPE minmax GRANULARITY 1 ) ENGINE = VersionedCollapsingMergeTree(sign, events) PARTITION BY toYYYYMM(start) @@ -83,11 +130,23 @@ CREATE TABLE plausible_events_db.ingest_counters `domain` LowCardinality(String), `site_id` Nullable(UInt64), `metric` LowCardinality(String), - `value` UInt64 + `value` UInt64, + `tracker_script_version` UInt16, + PROJECTION ingest_counters_site_traffic_projection + ( + SELECT + site_id, + toDate(event_timebucket), + sumIf(value, metric = 'buffered') + GROUP BY + site_id, + toDate(event_timebucket) + ) ) ENGINE = SummingMergeTree(value) -ORDER BY (domain, toDate(event_timebucket), metric, toStartOfMinute(event_timebucket)) -SETTINGS index_granularity = 8192; +PRIMARY KEY (domain, toDate(event_timebucket), metric, toStartOfMinute(event_timebucket)) +ORDER BY (domain, toDate(event_timebucket), metric, toStartOfMinute(event_timebucket), tracker_script_version) +SETTINGS index_granularity = 8192, deduplicate_merge_projection_mode = 'rebuild'; CREATE TABLE plausible_events_db.imported_visitors ( @@ -120,7 +179,8 @@ CREATE TABLE plausible_events_db.imported_sources `import_id` UInt64, `pageviews` UInt64, `referrer` String, - `utm_source` String + `utm_source` String, + `channel` LowCardinality(String) ) ENGINE = MergeTree ORDER BY (site_id, date, source) @@ -135,10 +195,12 @@ CREATE TABLE plausible_events_db.imported_pages `visitors` UInt64, `pageviews` UInt64, `exits` UInt64, - `time_on_page` UInt64, `import_id` UInt64, `visits` UInt64, - `active_visitors` UInt64 + `total_scroll_depth` UInt64, + `total_scroll_depth_visits` UInt64, + `total_time_on_page` UInt64, + `total_time_on_page_visits` UInt64 ) ENGINE = MergeTree ORDER BY (site_id, date, hostname, page) @@ -262,6 +324,23 @@ ENGINE = MergeTree ORDER BY (site_id, date, browser) SETTINGS index_granularity = 8192, replicated_deduplication_window = 0; +CREATE DICTIONARY plausible_events_db.failed_batches_dict +( + `batch` UInt64 +) +PRIMARY KEY batch +SOURCE(CLICKHOUSE(TABLE failed_batches DB 'plausible_events_db' INVALIDATE_QUERY 'SELECT max(batch) FROM failed_batches')) +LIFETIME(MIN 300 MAX 360) +LAYOUT(HASHED()); + +CREATE TABLE plausible_events_db.failed_batches +( + `batch` UInt64 +) +ENGINE = MergeTree +ORDER BY batch +SETTINGS index_granularity = 8192; + CREATE TABLE plausible_events_db.events_v2 ( `timestamp` DateTime CODEC(Delta(4), LZ4), @@ -304,7 +383,13 @@ CREATE TABLE plausible_events_db.events_v2 `country_name` String ALIAS dictGet('plausible_events_db.location_data_dict', 'name', ('country', country_code)), `region_name` String ALIAS dictGet('plausible_events_db.location_data_dict', 'name', ('subdivision', subdivision1_code)), `city_name` String ALIAS dictGet('plausible_events_db.location_data_dict', 'name', ('city', city_geoname_id)), - `channel` LowCardinality(String) + `channel` LowCardinality(String), + `click_id_param` LowCardinality(String), + `scroll_depth` UInt8, + `acquisition_channel` LowCardinality(String) MATERIALIZED multiIf(position(lower(utm_campaign), 'cross-network') > 0, 'Cross-network', lower(utm_medium) IN ('display', 'banner', 'expandable', 'interstitial', 'cpm'), 'Display', match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') AND ((dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SHOPPING') OR match(lower(utm_campaign), '^(.*(([^a-df-z]|^)shop|shopping).*)$')), 'Paid Shopping', ((dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SEARCH') AND (match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') OR dictHas('plausible_events_db.acquisition_channel_paid_sources_dict', lower(utm_source)))) OR ((lower(referrer_source) = 'google') AND (click_id_param = 'gclid')) OR ((lower(referrer_source) = 'bing') AND (click_id_param = 'msclkid')), 'Paid Search', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SOCIAL') AND (match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') OR dictHas('plausible_events_db.acquisition_channel_paid_sources_dict', lower(utm_source))), 'Paid Social', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_VIDEO') AND (match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$') OR dictHas('plausible_events_db.acquisition_channel_paid_sources_dict', lower(utm_source))), 'Paid Video', match(lower(utm_medium), '^(.*cp.*|ppc|retargeting|paid.*)$'), 'Paid Other', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SHOPPING') OR match(lower(utm_campaign), '^(.*(([^a-df-z]|^)shop|shopping).*)$'), 'Organic Shopping', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SOCIAL') OR (lower(utm_medium) IN ('social', 'social-network', 'social-media', 'sm', 'social network', 'social media')), 'Organic Social', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_VIDEO') OR (position(lower(utm_medium), 'video') > 0), 'Organic Video', dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_SEARCH', 'Organic Search', (dictGet('plausible_events_db.acquisition_channel_source_category_dict', 'category', lower(referrer_source)) = 'SOURCE_CATEGORY_EMAIL') OR match(lower(utm_source), 'e[-_ ]?mail|newsletter') OR match(lower(utm_medium), 'e[-_ ]?mail|newsletter'), 'Email', lower(utm_medium) = 'affiliate', 'Affiliates', lower(utm_medium) = 'audio', 'Audio', lower(utm_source) = 'sms', 'SMS', lower(utm_medium) = 'sms', 'SMS', endsWith(lower(utm_medium), 'push') OR multiSearchAny(lower(utm_medium), ['mobile', 'notification']) OR (lower(referrer_source) = 'firebase'), 'Mobile Push Notifications', (lower(utm_medium) IN ('referral', 'app', 'link')) OR (NOT empty(lower(referrer_source))), 'Referral', 'Direct'), + `engagement_time` UInt32, + `batch` UInt64, + INDEX minmax_batch batch TYPE minmax GRANULARITY 1 ) ENGINE = MergeTree PARTITION BY toYYYYMM(timestamp) @@ -313,6 +398,42 @@ ORDER BY (site_id, toDate(timestamp), name, user_id, timestamp) SAMPLE BY user_id SETTINGS index_granularity = 8192; +CREATE DICTIONARY plausible_events_db.acquisition_channel_source_category_dict +( + `referrer_source` String, + `category` String +) +PRIMARY KEY referrer_source +SOURCE(CLICKHOUSE(TABLE acquisition_channel_source_category DB 'plausible_events_db')) +LIFETIME(MIN 0 MAX 0) +LAYOUT(COMPLEX_KEY_HASHED()); + +CREATE TABLE plausible_events_db.acquisition_channel_source_category +( + `referrer_source` String, + `category` LowCardinality(String) +) +ENGINE = MergeTree +ORDER BY referrer_source +SETTINGS index_granularity = 8192; + +CREATE DICTIONARY plausible_events_db.acquisition_channel_paid_sources_dict +( + `referrer_source` String +) +PRIMARY KEY referrer_source +SOURCE(CLICKHOUSE(TABLE acquisition_channel_paid_sources DB 'plausible_events_db')) +LIFETIME(MIN 0 MAX 0) +LAYOUT(COMPLEX_KEY_HASHED()); + +CREATE TABLE plausible_events_db.acquisition_channel_paid_sources +( + `referrer_source` String +) +ENGINE = MergeTree +ORDER BY referrer_source +SETTINGS index_granularity = 8192; + CREATE TABLE plausible_events_db.schema_migrations ( `version` Int64, @@ -331,36 +452,84 @@ SOURCE(CLICKHOUSE(TABLE location_data DB 'plausible_events_db')) LIFETIME(MIN 0 MAX 0) LAYOUT(COMPLEX_KEY_CACHE(SIZE_IN_CELLS 500000)); +CREATE DICTIONARY plausible_events_db.failed_batches_dict +( + `batch` UInt64 +) +PRIMARY KEY batch +SOURCE(CLICKHOUSE(TABLE failed_batches DB 'plausible_events_db' INVALIDATE_QUERY 'SELECT max(batch) FROM failed_batches')) +LIFETIME(MIN 300 MAX 360) +LAYOUT(HASHED()); + +CREATE DICTIONARY plausible_events_db.acquisition_channel_source_category_dict +( + `referrer_source` String, + `category` String +) +PRIMARY KEY referrer_source +SOURCE(CLICKHOUSE(TABLE acquisition_channel_source_category DB 'plausible_events_db')) +LIFETIME(MIN 0 MAX 0) +LAYOUT(COMPLEX_KEY_HASHED()); + +CREATE DICTIONARY plausible_events_db.acquisition_channel_paid_sources_dict +( + `referrer_source` String +) +PRIMARY KEY referrer_source +SOURCE(CLICKHOUSE(TABLE acquisition_channel_paid_sources DB 'plausible_events_db')) +LIFETIME(MIN 0 MAX 0) +LAYOUT(COMPLEX_KEY_HASHED()); + INSERT INTO "plausible_events_db"."schema_migrations" (version, inserted_at) VALUES -(20200915070607,'2024-09-11 09:23:21'), -(20200918075025,'2024-09-11 09:23:21'), -(20201020083739,'2024-09-11 09:23:21'), -(20201106125234,'2024-09-11 09:23:21'), -(20210323130440,'2024-09-11 09:23:21'), -(20210712214034,'2024-09-11 09:23:21'), -(20211017093035,'2024-09-11 09:23:21'), -(20211112130238,'2024-09-11 09:23:21'), -(20220310104931,'2024-09-11 09:23:21'), -(20220404123000,'2024-09-11 09:23:21'), -(20220421161259,'2024-09-11 09:23:21'), -(20220422075510,'2024-09-11 09:23:21'), -(20230124140348,'2024-09-11 09:23:21'), -(20230210140348,'2024-09-11 09:23:21'), -(20230214114402,'2024-09-11 09:23:21'), -(20230320094327,'2024-09-11 09:23:21'), -(20230417104025,'2024-09-11 09:23:21'), -(20230509124919,'2024-09-11 09:23:21'), -(20231017073642,'2024-09-11 09:23:21'), -(20240123142959,'2024-09-11 09:23:21'), -(20240209085338,'2024-09-11 09:23:21'), -(20240220123656,'2024-09-11 09:23:21'), -(20240222082911,'2024-09-11 09:23:21'), -(20240305085310,'2024-09-11 09:23:21'), -(20240326134840,'2024-09-11 09:23:21'), -(20240327085855,'2024-09-11 09:23:21'), -(20240419133926,'2024-09-11 09:23:21'), -(20240423094014,'2024-09-11 09:23:21'), -(20240502115822,'2024-09-11 09:23:21'), -(20240709181437,'2024-09-11 09:23:22'), -(20240801091615,'2024-09-11 09:23:22'), -(20240829092858,'2024-09-11 09:23:22'); +(20200915070607,'2025-09-12 16:26:41'), +(20200918075025,'2025-09-12 16:26:41'), +(20201020083739,'2025-09-12 16:26:41'), +(20201106125234,'2025-09-12 16:26:41'), +(20210323130440,'2025-09-12 16:26:41'), +(20210712214034,'2025-09-12 16:26:41'), +(20211017093035,'2025-09-12 16:26:41'), +(20211112130238,'2025-09-12 16:26:41'), +(20220310104931,'2025-09-12 16:26:41'), +(20220404123000,'2025-09-12 16:26:41'), +(20220421161259,'2025-09-12 16:26:41'), +(20220422075510,'2025-09-12 16:26:41'), +(20230124140348,'2025-09-12 16:26:41'), +(20230210140348,'2025-09-12 16:26:41'), +(20230214114402,'2025-09-12 16:26:41'), +(20230320094327,'2025-09-12 16:26:41'), +(20230417104025,'2025-09-12 16:26:41'), +(20230509124919,'2025-09-12 16:26:41'), +(20231017073642,'2025-09-12 16:26:41'), +(20240123142959,'2025-09-12 16:26:41'), +(20240209085338,'2025-09-12 16:26:41'), +(20240220123656,'2025-09-12 16:26:41'), +(20240222082911,'2025-09-12 16:26:41'), +(20240305085310,'2025-09-12 16:26:41'), +(20240326134840,'2025-09-12 16:26:41'), +(20240327085855,'2025-09-12 16:26:41'), +(20240419133926,'2025-09-12 16:26:41'), +(20240423094014,'2025-09-12 16:26:41'), +(20240502115822,'2025-09-12 16:26:41'), +(20240709181437,'2025-09-12 16:26:42'), +(20240801091615,'2025-09-12 16:26:42'), +(20240829092858,'2025-09-12 16:26:42'), +(20241020114559,'2025-09-12 16:26:42'), +(20241028142653,'2025-09-12 16:26:42'), +(20241029074741,'2025-09-12 16:26:42'), +(20241104082248,'2025-09-12 16:26:42'), +(20241111084056,'2025-09-12 16:26:42'), +(20241112222848,'2025-09-12 16:26:42'), +(20241118112238,'2025-09-12 16:26:42'), +(20241120064325,'2025-09-12 16:26:42'), +(20241216133031,'2025-09-12 16:26:42'), +(20241218102326,'2025-09-12 16:26:42'), +(20241231083407,'2025-09-12 16:26:42'), +(20250212100953,'2025-09-12 16:26:42'), +(20250218094453,'2025-09-12 16:26:42'), +(20250219093806,'2025-09-12 16:26:42'), +(20250221124625,'2025-09-12 16:26:42'), +(20250304074501,'2025-09-12 16:26:42'), +(20250312063938,'2025-09-12 16:26:42'), +(20250316182725,'2025-09-12 16:26:42'), +(20250911084719,'2025-09-12 16:26:42'), +(20250912094034,'2025-09-12 16:26:42');