Um colega me perguntou recentemente como mover um par de bilhões de registros para o armazenamento de big data espaço-temporal (STBDS) do GeoEvent no local de um cliente, usando ArcGIS Data Interoperability. São posições arquivadas de veículos para uma empresa de serviços públicos, armazenadas em um geodatabase Oracle. Alguns milhões de novos eventos chegam diariamente. Os dados arquivados estão em mais de 70 tabelas.<\/P>
Como um serviço de feição no STBDS tem a mesma API REST que um serviço de feição hospedado comum, o plano inicial era aproveitar o padrão descrito no meu blog anterior<\/A>, ou seja, ETL<\/STRONG> os dados arquivados em itens shapefile do portal e então usar o endpoint Append REST da camada alvo para carregar os dados assincronamente. Uma complicação adicional era paralelizar o ETL como múltiplos trabalhos concorrentes. Essa abordagem reduziria o risco de falhas na rede enquanto transmitia pequenas transações (1000 feições é o tamanho padrão do lote) para o portal. Aconteceu que o ambiente do cliente não estava em uma versão que suportasse esse fluxo de trabalho e acabamos optando por streaming, mas isso me fez pensar em como fazer ETL de big data para a nova geração de armazéns na nuvem que você pode usar nativamente, e no ArcGIS Pro 2.9<\/STRONG>.<\/P>Minha mensagem básica é que você pode fazer esses trabalhos de lift and shift movendo grandes conjuntos de dados como arquivos, em múltiplos trabalhos concorrentes, maximizando assim a taxa de transferência e minimizando o risco no transporte. Vamos ver como.<\/STRONG><\/P>Armazéns na nuvem como Snowflake<\/STRONG>, Big Query<\/STRONG> e Redshift<\/STRONG> podem ser consultados e lidos no ArcGIS Pro 2.9, mas não escritos usando as ferramentas padrão. No entanto, ArcGIS Data Interoperability pode escrever nesses armazéns<\/STRONG>, incluindo dados espaciais, mas o modo padrão é streaming, que pode não escalar conforme sua necessidade. Vou mostrar um padrão que você pode usar nos três armazéns:<\/P>ETL conjuntos inteiros de dados, incluindo dados espaciais, como Apache Parquet<\/STRONG> ou outro formato como CSVA geometria é codificada em um formato padrão baseado em caracteres<\/LI><\/UL><\/LI>Automatize o ETL em múltiplos processos concorrentes<\/STRONG><\/LI>Não escreva nenhum código<\/STRONG> além dos comandos SQL ou macros exigidos pelo ambiente alvo<\/LI><\/UL>Também vou dar uma dica para qualquer programador escondido no meu espaço de blog sem código, veja abaixo 😉<\/span>, ou seja, algumas dicas em Python sobre criação de arquivos Parquet (Nota: Arquivos Parquet são um tipo suportado no ArcGIS Online desde 22 de setembro de 2021. Compartilhe alguns!)
<\/span>Bilhes de feis esto no escopo com esse padro mas estou usando um conjunto de dados mais modesto para propósitos de demonstrao, apenas 2.3 milhes de feis pontuais.<\/P>
2.3 Milhes de Feis Pontuais<\/span><\/span><\/P>Meus dados esto em 12 classes de feis, voc pode ter qualquer n
ero. O padro que vou mostrar funciona com dados divididos em partes separadas que podem ser processadas concorrentemente. Se seus dados s
o monolitos ento divida-os voc mesmo espacialmente (rede orientada algu
em? <\A>) ou adicionando um campo que signifique um identificador do lote populado pela posi
ao da linha - voc pode descartar esse campo durante o processamento.<\/P>Mencionei os armazens Snowflake, Big Query e Redshift. Em todos os casos voc pode preparar arquivos Parquet onde o ambiente alvo possa var eles e depois carregar a partir dos arquivos Parquet. Para dados espaciais, os arquivos Parquet precisaro ter a geometria codificada num formato entendido pelo ambiente alvo (
Snowflake<\A> e Big Query<\A> suportam GeoJSON e WKT, Redshift<\A> suporta WKT). Vou fornecer apenas um exemplo prático com GeoJSON indo para Snowflake. Meus dados demo s
o geometria pontual e o campo que uso para armazenar o GeoJSON tem largura 100, se voc estiver usando polilinha ou poligonal deve investigar quão larga sua feição mais rica em pontos fica ao codificar esse campo. Por exemplo selecionei um polígono muito rico em pontos numa camada e como GeoJSON ele tem 2.071.156 caracteres:<\/P> <\P>
with arcpy.da.SearchCursor('NZ Property Titles','shape@') as cursor:GoogleBigQueryConnector<\A> que pode carregar CSV para tabelas. Isso pode ser mais simples do que enviar Parquet e usar o ambiente bq command para carregar dados, eu nao investiguei esse cenário.<\P>Vamos analisar meu fluxo específico. O segredo é criar duas<\STRONG> ferramentas ETL, a primeira gerencia os trabalhos e chama um transformador
WorkspaceRunner<\A> que chama a segunda ferramenta, que faz o trabalho. É muito simples, aqui está LoadManager.fmw<\STRONG>, ele recebe uma lista de argumentos, no meu caso nomes das classes de feição num geodatabase:<\P>
LoadManager<\span><\span><\P> WorkspaceRunner inicia até 7 processos FME que executam uma ferramenta alvo até a fila de trabalhos ser consumida. O processamento provavelmente será limitado pela CPU enquanto os processos trabalhadores extraem, codificam e enviam os arquivos do conjunto de dados. Permiti que cada processo execute dois trabalhos que consumiram meus conjuntos entrantes em 6 processos.<\P>
< span class= "lia-inline-image-caption " onclick= "event.preventDefault(); " > WorkspaceRunner < \ span > < \ span > < \ P >< P > & nbsp ; < \ P >< P > Aqui está < STRONG > LoadWorkerParquet.fmw < \ STRONG > . < \ P >< P >< span class= "lia-inline-image-display-wrapper lia-image-align-center " image-alt= "LoadWorkerParquet " style= "width: 400px; " >< img src= "https:\/ \/ us.v-cdn.net \/6038851 \/uploads \/images \/23280i68720B57C8DAA789 \/LoadWorkerParquet.jpg " role= "button " title= "LoadWorkerParquet.jpg " alt= "LoadWorkerParquet " \/ >< span class= "lia-inline-image-caption " onclick= "event.preventDefault(); " > LoadWorkerParquet < \ span > < \ span > < \ P > Ele também é uma ferramenta simples, lê geodatabase, escreve um arquivo parquet local e depois envia o arquivo parquet para Snowflake onde os dados são copiados para uma tabela. Vou deixar vocês inspecionarem o SQLExecutor por conta própria mas como ajuda para entendimento, após substituição das variáveis aqui está como uma instrução fica (para Snowflake):<\P> <\P>create or replace file format Canterburyparquet_format
type = 'parquet';
create or replace temporary stage stageCanterbury
file_format = Canterburyparquet_format;
put file:\/C:\Work\Parquet\TitlesCanterbury.parquet @stageCanterbury;
copy into \"INTEROPERABILITY\".\"PUBLIC\".\"Titles\"
from (select
$1:id::number,
$1:title_no::varchar,
$1:status::varchar,
$1:type::varchar,
$1:land_district::varchar,
$1:issue_date::timestamp,
$1:guarantee_status::varchar,
$1:estate_description::varchar,
$1:number_owners::varchar,
$1:spatial_extents_shared::varchar,
to_geography($1:geom::varchar)
from @stageCanterbury);<\code><\pre> <\P>Uma vez que o arquivo parquet chega ao Snowflake a ingestão é extremamente rápida. A propósito, aprendi para fazer isso lendo a ajuda, eu não sou um DBA Snowflake.<\/P>
Qual desempenho você deve esperar? No momento da escrita, estou preso em casa como o resto de nós, mas na minha WiFi doméstica eu carrego 2,3 milhões de features para o Snowflake em 6 minutos, então com um computador decente e rede cabeada eu acho conservadoramente 25 milhões de features point<\/STRONG> por hora. Claro que para um ambiente de produção e um trabalho realmente grande você poderia usar múltiplos computadores, certamente os armazéns de nuvem alvo escalarão para suportar a taxa de transferência.<\/P>No download do blog você notará que incluo uma segunda ferramenta worker, LoadWorker.fmw<\/STRONG>, isso foi para eu comparar o desempenho da forma usual de escrever para o Snowflake com 100K features por transação, foi muito mais lento.<\/P>Agora de volta ao core Pro 2.9 meus dados estão carregados no Snowflake e posso lançar consultas e aproveitar a experiência de computação escalada.<\/P>
Snowflake no Painel de Catálogo<\/span><\/span><\/P>Mencionei uma opção Python para criar arquivos parquet, está no download do blog mas aqui está também:<\/P> <\/P># Exemplo Pro 2.9+ criação de um arquivo parquet a partir de uma classe de feição
# Geometria é codificada como GeoJSON em um campo 'geom'
import arcpy
import pyarrow.parquet as pq
arcpy.env.overwriteOutput = True
# Classe de feição fonte
Canterbury = r"C:\Work\Parquet\Parquet.gdb\Canterbury"
# Criar classe de feição em memória em WGS84
with arcpy.EnvManager(outputCoordinateSystem='GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]',
geographicTransformations="NZGD_2000_To_WGS_1984_1"):
arcpy.conversion.ExportFeatures(
in_features=Canterbury,
out_features=r"memory\Canterbury")
# Adicionar o campo geom (geometria não point requererá um campo maior)
Canterbury = arcpy.management.AddField("memory\Canterbury","geom","TEXT",None,None,100,'',"NULLABLE","NON_REQUIRED",'').getOutput(0)
# Derivar GeoJSON
with arcpy.da.UpdateCursor(Canterbury,['shape@','geom']) as cursor:
for row in cursor:
row[1] = str(row[0].__geo_interface__)
cursor.updateRow(row)
# Remover geometria criando uma Tabela
esriTable = arcpy.conversion.TableToTable(Canterbury,"memory","CanterburyTable").getOutput(0)
# Criar tabela arrow
arrowTable = arcpy.da.TableToArrowTable(esriTable)
# Escrever parquet
pq.write_table(arrowTable,r'C:\Work\Parquet\TitlesCanterbury.parquet',
version='1.0',
compression='SNAPPY')<\/code><\/pre> <\/P>Divirta-se movendo esses dados em escala!<\/P> <\/P>