Een collega vroeg me onlangs hoe je een paar miljard records kunt verplaatsen naar GeoEvent's spatiotemporale big data store (STBDS) bij een klant, met gebruik van ArcGIS Data Interoperability. Dit zijn gearchiveerde voertuigposities voor een nutsbedrijf, opgeslagen in een Oracle geodatabase. Er komen dagelijks een paar miljoen nieuwe gebeurtenissen binnen. De gearchiveerde data staat in meer dan 70 tabellen.<\/P>
Aangezien een feature service in de STBDS dezelfde REST API heeft als een gewone gehoste feature service, was het plan om het patroon te gebruiken dat beschreven staat in mijn eerdere blog, namelijk om de ETL van de archiefdata naar portal shapefile items te doen en vervolgens de Append REST endpoint van de doellaag asynchroon te gebruiken om de data te laden. Een extra complicatie was om de ETL te paralleliseren als meerdere gelijktijdige taken. Deze aanpak zou het risico op netwerkuitval verminderen tijdens het streamen van kleine transacties (1000 features is de standaard batchgrootte) naar het portal. Het bleek dat de klantomgeving niet op een release draaide die deze workflow ondersteunde en we zijn toch voor streaming gegaan, maar het zette me aan het denken over hoe je big data kunt ETL'en naar de nieuwe generatie cloud warehouses die je native kunt gebruiken, en in ArcGIS Pro 2.9.<\/P>
Mijn basisboodschap is dat je deze lift-and-shift taken kunt uitvoeren door grote datasets als bestanden te verplaatsen, in meerdere gelijktijdige taken, waardoor je doorvoer maximaliseert en transportrisico minimaliseert. Laten we eens kijken hoe.<\/STRONG><\/P>Cloud warehouses zoals Snowflake, Big Query en Redshift kunnen worden bevraagd en gelezen in ArcGIS Pro 2.9, maar niet beschreven met standaardtools. ArcGIS Data Interoperability kan echter wel naar deze warehouses schrijven, inclusief ruimtelijke data, maar de standaardmodus is streaming, wat mogelijk niet schaalt zoals je nodig hebt. Ik ga je een patroon laten zien dat je kunt gebruiken voor alle drie warehouses:<\/P>ETL volledige datasets, inclusief ruimtelijke data, als Apache Parquet of een ander formaat zoals CSVGeometrie wordt gecodeerd in een karaktergebaseerd standaardformaat<\/LI><\/UL><\/LI>Automatiseer de ETL in meerdere gelijktijdige processen<\/STRONG><\/LI>Schrijf geen code behalve eventuele SQL- of macro-opdrachten die vereist zijn door de doelomgeving<\/LI><\/UL>Ik geef ook wat tips aan coders die misschien meekijken in mijn no-code blogruimte, zie hieronder 😉<\/span>, dat wil zeggen wat Python-tips over het maken van Parquet-bestanden (Opmerking: Parquet-bestanden zijn sinds 22 september 2021 een ondersteund itemtype in ArcGIS Online. Deel er gerust een paar!)
<\/span>Miliarden features vallen binnen dit patroon maar ik gebruik voor demonstratiedoeleinden een bescheidener dataset van slechts 2,3 miljoen puntfeatures.<\/P>
2.3 Miljoen Puntfeatures<\/span><\/span><\/P>Mijn data zit in 12 feature classes, je kunt er elk aantal hebben. Het patroon dat ik zal laten zien werkt met data verdeeld in aparte delen die gelijktijdig verwerkt kunnen worden. Als je data monolithisch is, splits deze dan zelf ruimtelijk (oriented fishnet anyone? ) of door een veld toe te voegen dat een batch-ID aangeeft gevuld op basis van rijpositie - je kunt het veld tijdens verwerking verwijderen.<\/P>Ik noemde Snowflake, Big Query en Redshift warehouses. In alle gevallen kun je Parquet-bestanden plaatsen waar de doelomgeving ze kan zien en vervolgens laden vanuit die Parquet-bestanden. Voor ruimtelijke data moeten de Parquet-bestanden geometrie gecodeerd hebben in een formaat dat begrepen wordt door de doelomgeving (Snowflake en Big Query ondersteunen GeoJSON en WKT, Redshift ondersteunt WKT). Ik geef alleen een uitgewerkt voorbeeld met GeoJSON naar Snowflake. Mijn demodata is puntgeometrie en het veld dat ik gebruik om GeoJSON op te slaan heeft een breedte van 100, als je polyline of polygon data gebruikt moet je onderzoeken hoe breed je meest puntrijke features zijn bij het coderen van het veld. Bijvoorbeeld ik selecteerde een zeer puntrijke polygon in een laag en als GeoJSON is deze 2.071.156 tekens lang:<\/P> <\/P>with arcpy.da.SearchCursor('NZ Property Titles','shape@') as cursor:
for row in cursor:
print(len(str(row[0].__geo_interface__)))
2071156<\/code><\/pre><P> <\/P><P>Let op dat Data Interoperability de decimale precisie kan regelen die gebruikt wordt door GeoJSON; voor geografische data is een waarde van 7 redelijk, dezelfde polygon gebruikt dan <SPAN>1.274.064 tekens. Bijvoorbeeld gaat de eerste coördinaat van <SPAN>(172.90677540000001,-41.12752416699993) <\/SPAN><SPAN>naar (<\/SPAN><SPAN>172.9067754,-41.1275242). Onthoud dat elke byte telt!<\/SPAN><\/P><P><STRONG>Opmerking:<\/STRONG> Voor Big Query heeft Data Interoperability een <A title="GoogleBigQueryConnector" href="https:\/\/docs.safe.com\/fme\/html\/FME_Desktop_Documentation\/FME_Transformers\/Transformers\/googlebigqueryconnector-pkg.htm" target="_blank" rel="noopener nofollow noreferrer">GoogleBigQueryConnector</A>-hubtransformer die CSV naar tabellen kan laden. Dit kan eenvoudiger zijn dan Parquet versturen en de bq command environment gebruiken om data te laden, ik heb dit scenario niet onderzocht.<\/P><P>Laten we mijn specifieke workflow induiken. Het geheime ingrediënt is om <STRONG>twee<\strong>} ETL-tools te maken, de eerste beheert de taken en roept een <A title=\"WorkspaceRunner\" href=\"https:\/\ /docs.safe.com\ /fme\ /html\ /FME_Desktop_Documentation\ /FME_Transformers\ /Transformers\ /workspacerunner.htm\ " target=\ "_blank\ " rel=\ "noopener nofollow noreferrer\ ">WorkspaceRunner<\ /a > transformer aan die de tweede tool aanroept, die het werk doet. Het is heel eenvoudig, hier is < STRONG >LoadManager.fmw< \ strong > , het neemt een lijst met argumenten aan, in mijn geval feature class namen in een geodatabase:< \ p >< p >< span class = " lia-inline-image-display-wrapper lia-image-align-center " image-alt = " LoadManager " style = " width: 400px ; " >< img src = " https :// us.v-cdn.net/6038851/uploads/images/23278i26931F24B6DB0008/LoadManager.jpg " role = " button " title = " LoadManager.jpg " alt = " LoadManager " / >< span class = " lia-inline-image-caption " onclick = " event.preventDefault(); " > LoadManager < / span >< / span >< / p >< p >& nbsp ; WorkspaceRunner start tot 7 FME-processen die een doelttool uitvoeren totdat de wachtrij met taken leeg is.& nbsp ; Verwerking zal waarschijnlijk CPU-bound zijn terwijl werkprocessen datasetbestanden extraheren, coderen en uploaden.& nbsp ; Ik liet elk proces twee taken uitvoeren wat mijn binnenkomende datasets met 6 processen verwerkte.< \ p >< p >< span class = " lia-inline-image-display-wrapper lia-image-align-center " image-alt = " WorkspaceRunner " style = " width: 400px ; " >< img src = " https :// us.v-cdn.net/6038851/uploads/images/23279i73187AEDC185DFB7/WorkspaceRunner.jpg " role = " button " title = " WorkspaceRunner.jpg " alt = " WorkspaceRunner " / >< span class = " lia-inline-image-caption " onclick = " event.preventDefault(); " > WorkspaceRunner < / span >< / span >< / p >< p >& nbsp ;< \ p >< p > Hier is < 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 >< p > Het is ook een eenvoudige tool, hij leest geodatabase, schrijft lokaal parquet-bestand en stuurt dit parquet-bestand naar Snowflake waar de data wordt gekopieerd naar een tabel.& nbsp ; Ik laat jullie zelf de SQLExecutor inspecteren maar ter verduidelijking ziet zo'n statement er na variabelensubstitutie zo uit (voor Snowflake):< \ p >< p >& nbsp ;< pre class = om dit te doen door <A title="de hulp te lezen" href="https://docs.snowflake.com/en/user-guide/script-data-load-transform-parquet.html#sql-script-1-load-parquet-data" target="_blank" rel="noopener nofollow noreferrer">de hulp te lezen</A>, ik ben geen Snowflake DBA.</P><P>Welke prestaties mag je verwachten? Op het moment van schrijven zit ik thuis vast zoals de meesten van ons, maar op mijn thuis-WiFi laad ik 2,3 miljoen features in Snowflake in 6 minuten, dus met een degelijke computer en bekabeld netwerk denk ik conservatief aan 25 miljoen <STRONG>point</STRONG> features per uur. Natuurlijk kun je voor een productieomgeving en een echt grote klus meerdere computers gebruiken, zeker de target cloud warehouses zullen opschalen om de doorvoer aan te kunnen.</P><P>In de blogdownload zul je merken dat ik een tweede worker tool toevoeg, <STRONG>LoadWorker.fmw</STRONG>, dit was voor mij om de prestaties te vergelijken van de gebruikelijke manier om naar Snowflake te schrijven met 100K features per transactie, dat was veel langzamer.</P><P>Nu terug in core Pro 2.9 is mijn data geladen in Snowflake en kan ik queries erop loslaten en genieten van de geschaalde compute-ervaring.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Snowflake in Catalog Pane" style="width: 268px;"><img src="https://us.v-cdn.net/6038851/uploads/images/23296iF3FD431AB7DF713E/Catalog.jpg" role="button" title="Catalog.jpg" alt="Snowflake in Catalog Pane" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Snowflake in Catalog Pane</span></span></P><P>Ik noemde een Python-optie voor het maken van parquet-bestanden, het staat in de blogdownload maar hier is het ook:</P><P> </P><pre class="lia-code-sample language-python"><code># Pro 2.9+ voorbeeld van het maken van een parquet-bestand vanuit een feature class
# Geometrie wordt gecodeerd als GeoJSON in een veld 'geom'
import arcpy
import pyarrow.parquet as pq
arcpy.env.overwriteOutput = True
# Bron feature class
Canterbury = r"C:\Work\Parquet\Parquet.gdb\Canterbury"
# Maak in-memory feature class in 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")
# Voeg het geom-veld toe (niet-point geometrie vereist een breder veld)
Canterbury = arcpy.management.AddField("memory\Canterbury","geom","TEXT",None,None,100,'',"NULLABLE","NON_REQUIRED",'').getOutput(0)
# Afleiden GeoJSON
with arcpy.da.UpdateCursor(Canterbury,['shape@','geom']) as cursor:
for row in cursor:
row[1] = str(row[0].__geo_interface__)
cursor.updateRow(row)
# Verwijder geometrie door een Table te maken
esriTable = arcpy.conversion.TableToTable(Canterbury,"memory","CanterburyTable").getOutput(0)
# Maak arrow table
arrowTable = arcpy.da.TableToArrowTable(esriTable)
# Schrijf parquet
pq.write_table(arrowTable,r'C:\Work\Parquet\TitlesCanterbury.parquet',
version='1.0',
compression='SNAPPY')
Veel plezier met het verplaatsen van die data op schaal!