Spark lokaal draaien tegen ADLS Gen2 met Floci AZ
We implementeerden de ADLS Gen2 DFS-semantiek die nodig is voor hadoop-azure:x.x.x en valideerde het resultaat via de volledige Titan Spark-pipeline over abfss://.
Titan / Spark
Analytics-ready
Hadoop ABFS
hadoop-azure:x.x.x
ADLS Gen2 DFS-semantiek
Fouten · ETags · list · rename · metadata
Floci AZ
Lokale Azure-compatibele storagegrens
Gevalideerd over drie Spark- en Hadoop-generaties
We hebben de DFS-implementatie niet slechts tegen één runtimecombinatie gevalideerd. Hetzelfde Floci AZ DFS-pad is uitgevoerd over onze Spark 3.5- en Spark 4.x-compatibiliteitsbaselines.
Baseline 1
DBR 16.4 LTS-compatibiliteit
- Spark
- 3.5.2
- Hadoop
- 3.3.4
- Client
- hadoop-azure:3.3.4
- Delta Lake
- 3.3.1
Baseline 2
DBR 17.3 LTS-compatibiliteit
- Spark
- 4.0.0
- Hadoop
- 3.4.1
- Client
- hadoop-azure:3.4.1
- Delta Lake
- 4.0.0
Baseline 3
DBR 18 LTS-compatibiliteit
- Spark
- 4.1.0
- Hadoop
- 3.4.2
- Client
- hadoop-azure:3.4.2
- Delta Lake
- 4.2.0
De Databricks Runtime-referenties zijn benaderende omgevingsanalogen ter oriëntatie. Het zijn geen claims van bit-for-bit runtime-equivalentie.
Waarom Floci AZ en niet LocalStack of Azurite
Titan is afhankelijk van cloud-facing contracten, niet alleen storagebytes. In lokale development willen we onze normale SDK's, Spark-filesystemcode en infrastructuurtooling laten draaien tegen Azure-shaped endpoints zonder voor iedere iteratie Azure-infrastructuur te provisionen.
Beslissing lokale emulator
Hoe de drie opties zich tot elkaar verhouden
De belangrijke vraag was niet welke emulator de meeste features heeft, maar welke Titan's Azure-facing development path het beste behoudt.
LocalStack
Geen match- Cloudgrens
- AWS-gericht
- ADLS Gen2 DFS
- × Niet het Azure DFS-contract
- Platformscope
- Brede service-emulatie, maar voor de verkeerde cloudgrens
- Geschikt voor Titan
- Nee
Azurite
Gedeeltelijke fit- Cloudgrens
- Azure Storage
- ADLS Gen2 DFS
- × Buiten gedocumenteerde scope
- Platformscope
- Emulatie van Blob-, Queue- en Table-storage
- Geschikt voor Titan
- Gedeeltelijk
Floci AZ
Geselecteerd- Cloudgrens
- Azure-gerichte services
- ADLS Gen2 DFS
- ✓ Ondersteund via onze DFS-contributie
- Platformscope
- Bredere Azure-emulatorfamilie met echte SDK- en CLI-clients
- Geschikt voor Titan
- ✓ Beste fit
Beslissing: Met Floci AZ kunnen we het productiegerichte Spark-, Hadoop- en Azure-clientpad in de lokale ontwikkelcyclus houden, terwijl we toch een implementatie hebben die we kunnen uitbreiden wanneer het benodigde protocoloppervlak ontbreekt.
De eerste Floci AZ / Hadoop ABFS-compatibiliteitsfailure
Hadoop riep aan FileSystem.delete() via het DFS-endpoint. Vóór de patch liep die DELETE nog via floci-az's generieke Blob-deletepad. Een ontbrekend pad leek daardoor op een ontbrekende blob.
Waargenomen vóór de patch
Blob-achtige ontbrekende objectmelding
HTTP 404
Content-Type: application/xml
x-ms-error-code: BlobNotFound
<...>
<Code>BlobNotFound</Code>
...
</...>
Alleen de velden die door de regressietooling zijn geverifieerd worden getoond. We claimen geen exacte originele XML-envelope die nooit als fixture is vastgelegd.
Wat Hadoop ABFS nodig had
Data Lake ontbrekend pad
HTTP 404
Content-Type: application/json
x-ms-error-code: PathNotFound
{
"error": {
"code": "PathNotFound",
"message": "The specified path does not exist."
}
}
Met DFS-semantiek vertaalt Hadoop de ontbrekende delete naar het normale filesystemresultaat: false.
Hoe één DELETE voor een ontbrekend pad wordt vertaald
HTTP-response → filesystem-resultaat
HTTP-transport
404 zegt dat de resource niet bestaat.
ADLS Gen2 DFS
PathNotFound + JSON identificeert het servicecontract van de Data Lake.
Hadoop ABFS
De client mapt de Azure-response naar Hadoop-filesystemsemantiek.
Caller-resultaat
fs.delete(path, true) == false
Voor Hadoop telt het contract achter de statuscode
ABFS vertaalt Azure-specifieke foutcodes, headers en de status van het bestandssysteem over meerdere verzoeken. Een correcte HTTP-status met de verkeerde servicebetekenis kan alsnog tot een onjuist Hadoop-bestandssysteemresultaat leiden. Dit contract geldt ook voor ETags, padtypen, hernoemingen en metadata-updates. Daarom moest de implementatie verder gaan dan alleen DELETE.
Draai lokaal het echte Spark- en Hadoop-ABFS-pad
Titan bereikt storage nog steeds via Spark en Hadoop ABFS. Lokale development verandert het endpoint dat de DFS-request ontvangt, niet het clientpad dat hem creëert.
De emulator versterkt de inner development loop. Hij vervangt integratietesten tegen Azure niet.
Map het ABFS-contract in plaats van één fout tegelijk na te jagen
Na een paar cycli waarin we één operatie repareerden om vervolgens het volgende probleem bloot te leggen, waren we het zat om steeds één klein stukje van de puzzel op te lossen.
We namen hadoop-azure:x.x.x en hadoop-common:x.x.x:
De ABFS-codepaden getraced
De DFS-calls reverse-engineered
Uitgebreide Floci AZ DFS-afhandeling
Dat veranderde het werk van reactief debuggen in een begrensde compatibiliteitsronde. We wisten welke filesystemoperaties bereikbaar waren, welke servicesemantiek Hadoop parseerde en welke states tussen calls moesten blijven bestaan.
Analytics
Van clientanalyse naar Titan E2E
Trace de client
Volg Hadoop-filesystemmethoden naar de concrete ABFS-requestpaden.
Map het bereikbare DFS-oppervlak
Identificeer filesystem lifecycle, path I/O, listing, metadata, ACL, leases, rename en delete.
Match client-observed semantics
Implementeer error envelopes, ETags, resource types, continuation en state transitions.
Voeg regressietests toe
Zet ieder ontdekt contract om in repository-level coverage.
Draai de echte Hadoop-client
Gebruik AzureBlobFileSystem als compatibiliteitscontrole op clientniveau.
Draai Titan end-to-end
Valideer iedere ondersteunde ABFSS-workflow van Source → Semantic.
Het DFS-contract dat hadoop-azure en hadoop-common vereisen
Het resultaat is breder dan het oorspronkelijke DELETE-issue omdat de echte ABFS-client afhankelijk is van een coherent DFS-filesystemcontract. We implementeerden de operaties die door de gecombineerde hadoop-azure en hadoop-common clientpad en valideerde dat contract vervolgens tegen de Spark 3.5.2-, 4.0.0- en 4.1.0-compatibiliteitsbaselines.
Create en properties
Bestandssysteem aanmaken/verwijderen plus filesystem-properties en rootstatus ophalen/instellen.
Create read append flush
Bestands- en directorycreatie, Path Read, sequentieel toevoegen en flushen.
ETags en overwrite
Conditional create en Hadoop's 409 → status/ETag → If-Match-overwriteflow.
Toon status rename delete
Exacte filelisting, continuation handling, recursive delete en file/directory rename.
Properties en XAttrs
Metadata-updates maken een round-trip zonder de onderliggende filebytes te wijzigen.
ACL-ownerpermission
Wire-compatible owner-, group-, permission- en ACL-metadata plus checkAccess.
Autodetectie en Path Lease
HNS-detectie, root-ACL/status en flows voor lease verkrijgen/vernieuwen/vrijgeven/breken/wijzigen.
DFS JSON-normalisatie
Data Lake-errorcodes en JSON-envelopes worden genormaliseerd voordat Hadoop ze ontvangt.
Scheid DFS-requests van Blob-gedrag
De belangrijke architectuurwijziging is servicescheiding. Een request voor *.dfs.core.windows.net moet worden geïdentificeerd voordat opslaggedrag wordt gekozen. Niet-ondersteunde DFS-updates worden expliciet geweigerd in plaats van door te vallen naar generieke Blob-mutatielogica.
Fail closed
Metadata-calls mogen nooit file uploads worden
Hadoop-metadataoperaties hebben vaak lege request bodies. Als een onbekende DFS PUT doorviel naar generieke PutBlob, kon de emulator een bestaand bestand vervangen door nul bytes.
Regel: ondersteunde DFS-operatie of expliciete DFS-fout. Geen stille Blob-fallback.
HTTP/2-routing
De DFS-hostname kan via HTTP/2 authority binnenkomen
Het routingfilter legt de opgeloste request-URI-host vast vóór async dispatch. Dat werkt voor HTTP/2 authority én HTTP/1.1, met Host als fallback.
Leg de DFS-host vast vóór async dispatch
Dit is de daadwerkelijke branchcode. De resolved host wordt vastgelegd terwijl de JAX-RS requestcontext beschikbaar is en opgeslagen op AzureRequest en vervolgens genormaliseerd door BlobServiceHandler.
// AzureRoutingFilter.java
String h = requestContext.getUriInfo().getRequestUri().getHost();
if (h == null || h.isBlank()) {
h = requestContext.getHeaders().getFirst("Host");
if (h == null) {
h = requestContext.getHeaders().getFirst("host");
}
}
final String capturedHost = h;
// BlobServiceHandler.java
private static boolean isDataLakeRequest(AzureRequest request) {
String host = request.host();
if (host == null || host.isBlank()) {
return false;
}
String normalizedHost = host.trim().toLowerCase(Locale.ROOT);
int portSeparator = normalizedHost.indexOf(':');
if (portSeparator >= 0) {
normalizedHost = normalizedHost.substring(0, portSeparator);
}
return normalizedHost.endsWith(".dfs.core.windows.net");
}
Hoe een DFS-request door de implementatie loopt
De request wordt geclassificeerd als Data Lake-verkeer, naar de DFS-padimplementatie gestuurd, toegepast op lokale state en terugvertaald naar het responsecontract dat Hadoop verwacht.
Gebruik de echte Hadoop ABFS-client
REST-tests bewijzen endpointgedrag. Deze test bewijst dat de endpointsemantiek samen een filesystem vormt dat de daadwerkelijke Hadoop 3.3.4-client kan gebruiken.
Werkelijke testharness
Behoud Hadoop's DFS-hostname en wireshape
De Docker-compatibiliteitssetup mapt devstoreaccount1.dfs.core.windows.net naar loopback. Een transparante TCP-relay forwardt poort 80 naar floci-az:4577, zodat Hadoop nog steeds zijn normale DFS-request verstuurt.
Hadoop 3.3.4-standaarden fs.azure.always.use.https=true, daarom schakelt de test HTTPS alleen uit voor deze plaintext-loopback-harness.
void start() throws IOException {
serverSocket = new ServerSocket(
listenPort,
50,
InetAddress.getByName("127.0.0.1"));
running.set(true);
executor.submit(this::acceptLoop);
}
Expliciet binden aan 127.0.0.1 houdt de testrelay weg van de andere interfaces van de container.
String filesystem =
"hadoop-" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
URI uri = URI.create("abfs://" + filesystem + "@" + ACCOUNT_HOST);
assertEquals("3.3.4", VersionInfo.getVersion());
Configuration conf = new Configuration();
conf.set("fs.abfs.impl",
"org.apache.hadoop.fs.azurebfs.AzureBlobFileSystem");
conf.set("fs.azure.account.key." + ACCOUNT_HOST, EmulatorConfig.DEV_KEY);
conf.setBoolean("fs.azure.always.use.https", false);
conf.setBoolean("fs.azure.createRemoteFileSystemDuringInitialization", true);
conf.setBoolean("fs.azure.enable.conditional.create.overwrite", true);
conf.unset("fs.azure.account.hns.enabled." + ACCOUNT_HOST);
conf.unset("fs.azure.account.hns.enabled");
try (FileSystem fs = FileSystem.newInstance(uri, conf)) {
assertTrue(fs.getFileStatus(new Path("/")).isDirectory());
Path dir = new Path("/compat");
Path file = new Path(dir, "file.txt");
assertTrue(fs.mkdirs(dir));
write(fs, file, "first");
write(fs, file, "second");
assertEquals("second", read(fs, file));
byte[] xattr = "v8".getBytes(StandardCharsets.UTF_8);
fs.setXAttr(file, "user.floci_smoke", xattr);
assertArrayEquals(xattr, fs.getXAttr(file, "user.floci_smoke"));
FileStatus[] exact = fs.listStatus(file);
assertEquals(1, exact.length);
Path renamed = new Path(dir, "renamed.txt");
assertTrue(fs.rename(file, renamed));
assertFalse(fs.delete(new Path(dir, "missing.txt"), true));
assertTrue(fs.delete(dir, true));
}
Valideer de volledige Titan-pipeline op ABFSS
De volledige Titan application-level E2E-gate die hier wordt getoond gebruikt Spark 3.5.2 + Hadoop 3.3.4 + SecureAzureBlobFileSystem over abfss://. Dezelfde DFS-implementatie is ook gevalideerd tegen de hierboven vermelde compatibiliteitsbaselines Spark 4.0.0 / Hadoop 3.4.1 en Spark 4.1.0 / Hadoop 3.4.2.
Titan kruist herhaaldelijk de filesystemboundary tijdens het ontdekken van bronbestanden, lezen van meerdere formaten, schrijven van intermediate datasets, listen van resultaten en committen van Spark-output. De E2E-run verifieert dat die contracten samen werken over de complete pipeline.
Bronformaten end-to-end getest
Negen inputworkflows. Eén ABFSS-backed Titan-pipeline.
Houd de compatibility-claim uitvoerbaar
Het nuttige resultaat is niet één groene run. De repository bevat het Hadoop-compatibiliteitspad zodat een toekomstige wijziging tegen hetzelfde clientcontract kan worden gecontroleerd.
./mvnw test
# final result
Tests run: 930
Failures: 0
Errors: 0
Skipped: 0
make test-java-compat
# final result
Tests run: 263
Failures: 0
Errors: 0
Skipped: 2
BUILD SUCCESS
Breng filesystem-failures naar de lokale feedbackloop
Het belangrijkste voordeel is niet dat Azure ineens op een laptop draait. Het voordeel is dat hetzelfde Spark- en Hadoop-filesystempad nu kan worden uitgeoefend voordat een deployment Azure bereikt, waardoor protocol- en integratiefouten veel dichter bij de codewijziging komen die ze veroorzaakte.
Echt clientgedrag lokaal. Spark en Hadoop ABFS blijven in het uitvoeringspad in plaats van te worden vervangen door storage mocks.
Eerdere protocolfeedback. Onjuiste DFS-errors, listinggedrag, metadata-updates en rename-semantiek kunnen lokaal of in CI falen.
Schonere Azure-validatie. Cloudtests kunnen focussen op identity, private networking, servicelimieten en productiespecifiek gedrag.
Het resultaat is een kortere feedbackcyclus zonder te doen alsof een lokale emulator een vervanging is voor Azure zelf.
Kortere feedbackloop
Vang het storagecontract af vóór clouddeployment
Vragen over lokale ADLS Gen2, Hadoop ABFS en Spark
Praktische antwoorden over protocolkeuzes, testharness en scope van de Floci AZ-bijdrage.
Kan Apache Spark lokaal ADLS Gen2 gebruiken zonder een echt Azure Storage Account?
Ja, mits de emulator de DFS-wire-semantiek reproduceert die Hadoop ABFS verwacht. Het gaat niet alleen om dezelfde URLs accepteren; servicespecifieke errors, ETags, resourcetypen, listinggedrag en path-update-semantiek moeten eveneens overeenkomen met het clientcontract.
Waarom zorgde BlobNotFound versus PathNotFound ervoor dat FileSystem.delete() faalde?
Hadoop ABFS interpreteert de ADLS-servicefoutcode en JSON-body. Een Blob-achtige 404 is semantisch niet gelijk aan een DFS PathNotFound-response. Met de correcte DFS-response wordt het verwijderen van een ontbrekend pad vertaald naar het normale Hadoop-filesystemresultaat: false.
Welke Hadoop-implementaties zijn gevalideerd?
De repository-compatibiliteitssuite gebruikt direct hadoop-common 3.3.4 en hadoop-azure 3.3.4 met de echte org.apache.hadoop.fs.azurebfs.AzureBlobFileSystem-class. Aanvullende compatibiliteitsbaselines dekken Hadoop 3.4.1 met hadoop-azure 3.4.1 en Hadoop 3.4.2 met hadoop-azure 3.4.2 naast Spark 4.0.0 en 4.1.0.
Is abfss:// ook getest?
Ja. De Docker-compatibility harness gebruikt abfs:// over loopback-HTTP zodat de DFS-host netjes behouden blijft. Externe Spark-validatie gebruikte org.apache.hadoop.fs.azurebfs.SecureAzureBlobFileSystem en abfss://-paden.
Waarom staat fs.azure.always.use.https=false ingesteld in de compatibiliteitstest?
In de Hadoop 3.3.4 repository-compatibility-harness staat deze instelling standaard op true, zelfs voor abfs://. Die lokale harness stuurt bewust plaintext loopback-verkeer over poort 80, dus HTTPS wordt alleen voor die test uitgeschakeld. Dit verandert het runtime-routinggedrag van Floci niet.
Implementeert dit volledige ADLS Gen2 POSIX-security?
Nee. Owner-, group-, permission- en ACL-metadata maken een round-trip via het DFS/Hadoop-wireprotocol, maar de emulator probeert niet de volledige Azure POSIX-authorization-engine te reproduceren.
Hoe is de definitieve implementatie gevalideerd?
De definitieve branch doorstond 930 repositorytests en 263 Java-compatibiliteitstests zonder failures. De repositorysuite oefent direct de echte Hadoop 3.3.4 ABFS-client uit, de Titan application-level E2E-gate draait Spark 3.5.2 over abfss:// voor negen sourceworkflows en aanvullende compatibiliteitsbaselines valideren de DFS-implementatie met Spark 4.0.0 / Hadoop 3.4.1 en Spark 4.1.0 / Hadoop 3.4.2.
Waar staat de open-source-implementatie?
De implementatie is ontwikkeld voor het Floci AZ-project en is beschikbaar op de publieke featurebranch die vanuit dit artikel is gelinkt terwijl de upstream-contributie wordt beoordeeld.
Laat lokale dataplatformtests de echte integratiegrens testen
We ontwerpen Spark-, Databricks- en Azure-dataplatforms met herhaalbare infrastructuur en testgrenzen die verder gaan dan alleen applicatiecode.
Een strengere testgrens