如何在不丢失或复制任何记录的情况下移动或更改管道

Posted

技术标签:

【中文标题】如何在不丢失或复制任何记录的情况下移动或更改管道【英文标题】:How to Move or Alter a Pipe without missing or duplicating any records 【发布时间】:2020-03-17 18:36:57 【问题描述】:

关于管理管道的This page 建议了一个将副本更改为管道中的语句的过程。

    暂停管道(使用 ALTER PIPE ... SET PIPE_EXECUTION_PAUSED=true)。 查询SYSTEM$PIPE_STATUS函数,验证管道执行状态为PAUSED,挂起文件计数为0。 重新创建管道以更改定义中的 COPY 语句。选择其中一个 以下选项之一: 删除管道(使用 DROP PIPE)并创建它(使用 CREATE PIPE)。重新创建管道(使用 CREATE OR REPLACE PIPE 语法)。在内部,管道被删除并创建。 再次查询SYSTEM$PIPE_STATUS函数,确认管道执行状态为RUNNING。

但是,如果应在暂停和重新创建管道之间的时间加载文件,则此处没有步骤来刷新该间隙。即使这些步骤很快发生,我们也有文件丢失的例子。

虽然运行 ALTER PIPE REFRESH 会导致重复,因为复制历史记录与管道相关联。重新创建的管道没有此历史记录,将返回并重新加载所有内容。

有没有一种很好的方法来编写这样的更改以保证没有间隙或重叠?比如获取原始管道暂停时的时间戳,然后在刷新查询中使用该时间戳?

更新:我们构建了一个完整的流程和脚本组合来处理我们的场景。完整脚本包含在下面的答案中。

【问题讨论】:

【参考方案1】:

很遗憾,这是自动摄取雪管的当前限制,并且没有很好的解决方法。

关于历史与特定管道相关联的观点是绝对正确的。重新创建管道后,从技术上讲,您将拥有一个具有新历史的新管道。因此,ALTER PIPE REFRESH 将重新加载指定前缀下的所有内容,从而导致重复数据。

您可以使用 ALTER PIPE REFRESH 的“MODIFIED_DATE”选项并指定一个等于最后一个 pipe_recieve_time 的时间,但您可能会错过一些通知,因为没有保证收到的订单。

为了尽量减少重复数据的数量,您可以按更细化的日期时间结构(例如年/月/日/小时/分钟/...)来组织数据文件,并仅刷新与所在时间对应的文件夹您的管道“关闭”。但是,可能有一些文件已经被前一个管道加载,导致一些重复的数据。

【讨论】:

【参考方案2】:

我们编写了这个用于管理雪管的流程/脚本。 它包括验证和刷新在更改或移动管道时删除的文件。

它在某种程度上特定于我们的流程和标准,但应该根据用例轻松修改/概括。

/* =====================================================================
This script provides tools for managing, altering, migrating snowpipes
    and their underlying tables. These instructions may need to be tweaked
    depending on your exact use case.

Assuming the pipes and stages follow our standard naming conventions,
    you can find and replace <Database_Name>, <Schema_Name>, <Table_Name>
    with their respective values

======================================================================== */

------------------------------------------
-- Set up Context and Variables
------------------------------------------
--Set your context so you don’t accidently run scripts in the wrong place
use <Database_Name>.<Schema_Name>

--Pause the pipe, also giving us the pipe altered time
--Prefer to avoid running this at the top of the hour to make it easier to verify file loads below.
alter pipe <Table_Name>_PIPE set pipe_execution_paused = true;

--Get timestamp for when pipe was paused and set as variable $pipe_altered_time
set pipe_altered_time = (select last_altered::timestamp_tz from information_schema.pipes where pipe_name = '<Table_Name>_PIPE');

--View variable from above to verify that it was altered just now
select $pipe_altered_time;

------------------------------------------
-- Alter table as per request
------------------------------------------
--This step will change depending on the resquest. E.g. it can be skipped if you are just moving a pipe

    --Example for adding columns to a pipe
    alter table <Table_Name> add column <CREATE_DATETIME_UTC> <timestamp_NTZ>;
    alter table <Table_Name> add column <INCOMING_RAW_REQUEST_ID> <STRING>;
    alter table <Table_Name> add column <INITIAL_PING_ID> <INT>;
    --Viewing if new column was added
    --Note: Make sure the order of all of the columns matches the pipe column order below
    select top 10 * from <Table_Name>;

    --Example for migrating a pipe
    create table <New_Table_Name> clone <Old_Database_Name>.<Old_Schema_Name>.<Old_Table_Name>

------------------------------------------
-- Alter Pipe
------------------------------------------
--Altered pipe statement and turns the pipe on
--This DDL should be retrieved using `SELECT GET_DDL('pipe', '<Table_Name>_PIPE')` (in the same schema context) and then modified to add/adjust columns if needed.
--Make sure that extracted column names here come from the JSON attribute names, which should be provided in the request.
--Most Pipes should follow a very similar pattern to the example below
create or replace pipe <Table_Name>_PIPE
    auto_ingest=true 
    integration='EVENT_HUB_QUEUE' 
    as 
<
   COPY INTO <Table_Name>
    FROM (SELECT PARSE_JSON(HEX_DECODE_STRING($1:Body)) as JSON, 1 as IS_ACTIVE, metadata$filename, JSON:CreatedDateUtc, JSON:Id, JSON:InitialPingId FROM @<Table_Name>_STAGE)
>;

--"Refresh" pipe. Reprocess files that were loaded during the paused time frame. 
-- This can take a few minutes depending on the table and how long it was paused.
alter pipe <Table_Name>_PIPE REFRESH MODIFIED_AFTER = $pipe_altered_time;

-------------------------------------------------------------------------------------------------------
--VALIDATION
-------------------------------------------------------------------------------------------------------

------------------------------------------
-- Verify that new files are being processed as expected
------------------------------------------

--Data validation - see what files have been loaded
--It can take a while for new files to show up depending on the event_hub.
--The real thing you want to check is that the pipe is working, and you see files from after the time when the pipe was paused
--If there are new files coming in from Event_hub, but not making it to the table, the pipe may have errors that will help for troubleshooting
select LAST_LOAD_TIME, ROW_COUNT, FILE_NAME
  from table(information_schema.copy_history(Table_Name=>'<Table_Name>', start_time=> dateadd(hours, -2, current_timestamp())))
  ORDER BY LAST_LOAD_TIME DESC;

--Validate data made it to the new columns
--Make sure that there are new records since the $pipe_altered_time in the table (it might take a while for them to show up)
--  Validate that the new records look as expected
--  If there is confusion here, it would be appropriate to contact the requestor on if the data looks correct
select * from <Table_Name>
where <any new column name> is not null;

------------------------------------------
--compare files in the storage account vs files loaded into the table
------------------------------------------

--Choose an hour of the day for comparing (Generally the hour during which the pipe was paused ($pipe_altered_time))
--see what files have been loaded recently if needed
select distinct top 1000 file_name from <Table_Name>
order by file_name desc;

--run these to create variables
--Remove the 'hh' from the directory format below to query an entire day.
set hour_to_check = DATE_TRUNC('Hour', $pipe_altered_time)::timestamp_ntz;
set date_directory = to_varchar($hour_to_check, '/YYYY/MM/DD/hh');
set file_pattern = '''.*' || $date_directory || '.*''';
select $file_pattern;

-- Get the list of files in the storage account. 
-- This query can take 10+ minutes for storage accounts with many files
-- Note: This is currently not dynamic, and doesn't like variables. You need to manually enter the value from $file_pattern variable above
LIST @<Table_Name>_STAGE pattern= <'.*/2020/04/23/17.*'>;

-- Comparison stuff
-- Uses the output of the previous query and compares it to what's been loaded in the table
SELECT s.$1 as azurefilename, t.file_name as tablefilename
FROM
(
    table(RESULT_SCAN(LAST_QUERY_ID()))
    --If you need to specify the query id for the LIST results manually, use this instead
    --table(RESULT_SCAN('<abcabcab-00bd-74d1-0000-1d990c3c72aa>'))
) s
FULL OUTER JOIN 
(
    select distinct file_name from OUTGOING_RAW_REQUEST
    where file_name like '%' || $date_directory || '%'
) t
ON charindex(t.file_name, s.$1) > 0;

-- Check for mismatched files
-- Ideally, this list should be empty
select *
    from 
    (
        table(RESULT_SCAN(LAST_QUERY_ID()))
        --If you need to specify the query id for the comparison results manually, use this instead
        --table(RESULT_SCAN('<abcabcab-00bd-74d1-0000-1d990c3c72aa>'))
    )
    where $1 is null or $2 is null;

-- If the above list is empty. You're done.
-- If the above list returns files that were not loaded, then copy the missing files into table.
-- Get the copy into statement. From the pipe creation statement above or using `select get_ddl('pipe', '<pipe_name>')`
--      You'll need to cut out the pipe syntax and just use the `COPY INTO` statement
-- the file name format should generally look like `/2020/04/23/17/20/53/28.avro` and you may need to trim the results from above to match.
<COPY INTO OUTGOING_RAW_REQUEST FROM (SELECT PARSE_JSON(HEX_DECODE_STRING($1:Body)), '1'::numeric, metadata$filename FROM @OUTGOING_RAW_REQUEST_STAGE)>
files = (
'</2020/04/23/17/20/53/28.avro>',
'</2020/04/23/17/20/52/20.avro>');

【讨论】:

以上是关于如何在不丢失或复制任何记录的情况下移动或更改管道的主要内容,如果未能解决你的问题,请参考以下文章

在不丢失 Html 样式的情况下更改 NSAttributedString 中的字体大小 - Swift

如何在不删除或移动mysql中的表的情况下重命名数据库? [复制]

SQL Server,如何在不丢失数据的情况下创建表后设置自动增量?

如何在不丢失格式的情况下更改 Word.Range 文本

如何在不使用任何框架(如 PRISM 或任何导航服务)的情况下按照 Xamarin.Forms 中的 MVVM 架构进行导航?

Android:如何在不使用 JDBC、PHP 或任何其他网络服务的情况下将数据发送到 MySQL DB? [复制]