it-swarm.com.de

Wie kann die Partitionierung bei der Migration von Daten aus JDBC-Quellen optimiert werden?

Ich versuche, Daten aus einer Tabelle in der PostgreSQL-Tabelle in eine Hive-Tabelle in HDFS zu verschieben. Dazu habe ich mir folgenden Code ausgedacht:

  val conf  = new SparkConf().setAppName("Spark-JDBC").set("spark.executor.heartbeatInterval","120s").set("spark.network.timeout","12000s").set("spark.sql.inMemoryColumnarStorage.compressed", "true").set("spark.sql.orc.filterPushdown","true").set("spark.serializer", "org.Apache.spark.serializer.KryoSerializer").set("spark.kryoserializer.buffer.max","512m").set("spark.serializer", classOf[org.Apache.spark.serializer.KryoSerializer].getName).set("spark.streaming.stopGracefullyOnShutdown","true").set("spark.yarn.driver.memoryOverhead","7168").set("spark.yarn.executor.memoryOverhead","7168").set("spark.sql.shuffle.partitions", "61").set("spark.default.parallelism", "60").set("spark.memory.storageFraction","0.5").set("spark.memory.fraction","0.6").set("spark.memory.offHeap.enabled","true").set("spark.memory.offHeap.size","16g").set("spark.dynamicAllocation.enabled", "false").set("spark.dynamicAllocation.enabled","true").set("spark.shuffle.service.enabled","true")
  val spark = SparkSession.builder().config(conf).master("yarn").enableHiveSupport().config("Hive.exec.dynamic.partition", "true").config("Hive.exec.dynamic.partition.mode", "nonstrict").getOrCreate()
  def prepareFinalDF(splitColumns:List[String], textList: ListBuffer[String], allColumns:String, dataMapper:Map[String, String], partition_columns:Array[String], spark:SparkSession): DataFrame = {
        val colList                = allColumns.split(",").toList
        val (partCols, npartCols)  = colList.partition(p => partition_columns.contains(p.takeWhile(x => x != ' ')))
        val queryCols              = npartCols.mkString(",") + ", 0 as " + flagCol + "," + partCols.reverse.mkString(",")
        val execQuery              = s"select ${allColumns}, 0 as ${flagCol} from schema.tablename where period_year='2017' and period_num='12'"
        val yearDF                 = spark.read.format("jdbc").option("url", connectionUrl).option("dbtable", s"(${execQuery}) as year2017")
                                                                      .option("user", devUserName).option("password", devPassword)
                                                                      .option("partitionColumn","cast_id")
                                                                      .option("lowerBound", 1).option("upperBound", 100000)
                                                                      .option("numPartitions",70).load()
        val totalCols:List[String] = splitColumns ++ textList
        val cdt                    = new ChangeDataTypes(totalCols, dataMapper)
        hiveDataTypes              = cdt.gpDetails()
        val fc                     = prepareHiveTableSchema(hiveDataTypes, partition_columns)
        val allColsOrdered         = yearDF.columns.diff(partition_columns) ++ partition_columns
        val allCols                = allColsOrdered.map(colname => org.Apache.spark.sql.functions.col(colname))
        val resultDF               = yearDF.select(allCols:_*)
        val stringColumns          = resultDF.schema.fields.filter(x => x.dataType == StringType).map(s => s.name)
        val finalDF                = stringColumns.foldLeft(resultDF) {
          (tempDF, colName) => tempDF.withColumn(colName, regexp_replace(regexp_replace(col(colName), "[\r\n]+", " "), "[\t]+"," "))
        }
        finalDF
  }
    val dataDF = prepareFinalDF(splitColumns, textList, allColumns, dataMapper, partition_columns, spark)
    val dataDFPart = dataDF.repartition(30)
    dataDFPart.createOrReplaceTempView("preparedDF")
    spark.sql("set Hive.exec.dynamic.partition.mode=nonstrict")
    spark.sql("set Hive.exec.dynamic.partition=true")
    spark.sql(s"INSERT OVERWRITE TABLE schema.hivetable PARTITION(${prtn_String_columns}) select * from preparedDF")

Die Daten werden in die Hive-Tabelle eingefügt, die basierend auf prtn_String_columns: source_system_name, period_year, period_num dynamisch partitioniert ist.

Spark-Submit verwendet:

SPARK_MAJOR_VERSION=2 spark-submit --conf spark.ui.port=4090 --driver-class-path /home/fdlhdpetl/jars/postgresql-42.1.4.jar  --jars /home/fdlhdpetl/jars/postgresql-42.1.4.jar --num-executors 80 --executor-cores 5 --executor-memory 50G --driver-memory 20G --driver-cores 3 --class com.partition.source.YearPartition splinter_2.11-0.1.jar --master=yarn --deploy-mode=cluster --keytab /home/fdlhdpetl/fdlhdpetl.keytab --principal [email protected] --files /usr/hdp/current/spark2-client/conf/Hive-site.xml,testconnection.properties --name Splinter --conf spark.executor.extraClassPath=/home/fdlhdpetl/jars/postgresql-42.1.4.jar

Die folgenden Fehlermeldungen werden in den Executor-Protokollen generiert:

Container exited with a non-zero exit code 143.
Killed by external signal
18/10/03 15:37:24 ERROR SparkUncaughtExceptionHandler: Uncaught exception in thread Thread[SIGTERM handler,9,system]
Java.lang.OutOfMemoryError: Java heap space
    at Java.util.Zip.InflaterInputStream.<init>(InflaterInputStream.Java:88)
    at Java.util.Zip.ZipFile$ZipFileInflaterInputStream.<init>(ZipFile.Java:393)
    at Java.util.Zip.ZipFile.getInputStream(ZipFile.Java:374)
    at Java.util.jar.JarFile.getManifestFromReference(JarFile.Java:199)
    at Java.util.jar.JarFile.getManifest(JarFile.Java:180)
    at Sun.misc.URLClassPath$JarLoader$2.getManifest(URLClassPath.Java:944)
    at Java.net.URLClassLoader.defineClass(URLClassLoader.Java:450)
    at Java.net.URLClassLoader.access$100(URLClassLoader.Java:73)
    at Java.net.URLClassLoader$1.run(URLClassLoader.Java:368)
    at Java.net.URLClassLoader$1.run(URLClassLoader.Java:362)
    at Java.security.AccessController.doPrivileged(Native Method)
    at Java.net.URLClassLoader.findClass(URLClassLoader.Java:361)
    at Java.lang.ClassLoader.loadClass(ClassLoader.Java:424)
    at Sun.misc.Launcher$AppClassLoader.loadClass(Launcher.Java:331)
    at Java.lang.ClassLoader.loadClass(ClassLoader.Java:357)
    at org.Apache.spark.util.SignalUtils$ActionHandler.handle(SignalUtils.scala:99)
    at Sun.misc.Signal$1.run(Signal.Java:212)
    at Java.lang.Thread.run(Thread.Java:745)

Ich sehe in den Protokollen, dass der Lesevorgang mit der angegebenen Anzahl von Partitionen wie folgt ordnungsgemäß ausgeführt wird:

Scan JDBCRelation((select column_names from schema.tablename where period_year='2017' and period_num='12') as year2017) [numPartitions=50]

Unten ist der Zustand der Testamentsvollstrecker in Stufen:  enter image description here

 enter image description here

 enter image description here

 enter image description here

Die Daten werden nicht richtig partitioniert. Eine Partition ist kleiner, während die andere sehr groß wird. Hier liegt ein Schräglaufproblem vor. Beim Einfügen der Daten in die Hive-Tabelle schlägt der Job in der folgenden Zeile fehl: spark.sql(s"INSERT OVERWRITE TABLE schema.hivetable PARTITION(${prtn_String_columns}) select * from preparedDF"), aber ich verstehe, dass dies aufgrund des Problems mit dem Datenversatz geschieht.

Ich habe versucht, die Anzahl der Executoren zu erhöhen, den Executor-Speicher und den Treiberspeicher zu vergrößern. Ich habe versucht, den Datenframe nur als CSV-Datei zu speichern, anstatt ihn in einer Hive-Tabelle zu speichern.

Java.lang.OutOfMemoryError: GC overhead limit exceeded

Enthält der Code irgendetwas, das ich korrigieren muss? Kann mir jemand mitteilen, wie ich dieses Problem beheben kann?

6
Metadata
  1. Bestimmen Sie, wie viele Partitionen Sie angesichts der Eingabedatenmenge und der Clusterressourcen benötigen. Als Faustregel gilt, die Partitionseingabe sollte unter 1 GB liegen, sofern dies nicht unbedingt erforderlich ist. und streng kleiner als die Blockgrößenbegrenzung.

    Sie haben zuvor angegeben , dass Sie 1 TB von Datenwerten migrieren, die Sie in verschiedenen Posts (5 - 70) verwenden, wahrscheinlich zu niedrig sind, um einen reibungslosen Ablauf zu gewährleisten.

    Versuchen Sie, einen Wert zu verwenden, für den keine weitere repartitioning erforderlich ist.

  2. Kennen Sie Ihre Daten. 

    Analysieren Sie die im Dataset verfügbaren Spalten, um zu bestimmen, ob Spalten mit hoher Kardinalität und gleichmäßiger Verteilung auf die gewünschte Anzahl von Partitionen verteilt werden sollen. Dies sind gute Kandidaten für einen Importvorgang. Zusätzlich sollten Sie einen genauen Wertebereich festlegen.

    Aggregationen mit unterschiedlichem Zentralitäts- und Schiefe-Maß sowie Histogramme und grundlegende Zählungen pro Schlüssel sind gute Erkundungsinstrumente. Für diesen Teil ist es besser, Daten direkt in der Datenbank zu analysieren, anstatt sie an Spark zu übergeben.

    Abhängig vom RDBMS können Sie möglicherweise width_bucket (PostgreSQL, Oracle) oder eine entsprechende Funktion verwenden, um eine gute Vorstellung davon zu erhalten, wie Daten in Spark verteilt werden, nachdem partitionColumn, lowerBound, upperBound, numPartitons geladen wurden.

    s"""(SELECT width_bucket($partitionColum, $lowerBound, $upperBound, $numPartitons) AS bucket, COUNT(*)
    FROM t
    GROUP BY bucket) as tmp)"""
    
  3. Wenn es keine Spalten gibt, die die oben genannten Kriterien erfüllen, beachten Sie Folgendes:

    • Benutzerdefinierte erstellen und über freigeben. eine Sicht. Hashes über mehrere unabhängige Spalten sind normalerweise gute Kandidaten. Informationen zu den hier verwendbaren Funktionen finden Sie in Ihrem Datenbankhandbuch (DBMS_CRYPTO in Oracle, pgcrypto in PostgreSQL) *.
    • Durch die Verwendung unabhängiger Spalten, die zusammengenommen eine ausreichend hohe Kardinalität bieten.

      Wenn Sie in eine partitionierte Hive-Tabelle schreiben möchten, sollten Sie die Hive-Partitionierungsspalten in Betracht ziehen. Möglicherweise wird die Anzahl der später generierten Dateien begrenzt.

  4. Partitionierungsargumente vorbereiten

    • Wenn die in den vorherigen Schritten ausgewählte oder erstellte Spalte numerisch ist, geben Sie sie direkt als partitionColumn an und verwenden Sie zuvor ermittelte Bereichswerte, um lowerBound und upperBound zu füllen.

      Wenn gebundene Werte nicht die Eigenschaften von Daten widerspiegeln (min(col) für lowerBound, max(col) für upperBound), kann dies zu einem erheblichen Datenversatz führen. Im schlimmsten Fall, wenn Grenzen den Datenbereich nicht abdecken, werden alle Datensätze von einer einzelnen Maschine abgerufen. Dies macht sie nicht besser als gar keine Partitionierung.

    • Wenn die in den vorherigen Schritten ausgewählte Spalte kategorisch ist oder eine Gruppe von Spalten ist, generieren Sie eine Liste der Prädikate wechselseitig , die die Daten vollständig abdecken, in einer Form, die in einer SQL where-Klausel verwendet werden kann. 

      Zum Beispiel, wenn Sie eine Spalte A mit den Werten {a1, a2, a3} und die Spalte B mit den Werten {b1, b2, b3} haben:

      val predicates = for {
        a <- Seq("a1", "a2", "a3")
        b <- Seq("b1", "b2", "b3")
      } yield s"A = $a AND B = $b"
      

      Stellen Sie sicher, dass sich die Bedingungen nicht überschneiden und alle Kombinationen abgedeckt sind. Wenn diese Bedingungen nicht erfüllt sind, führt dies zu Duplikaten bzw. fehlenden Datensätzen. 

      Übergeben Sie die Daten als predicates-Argument an den jdbc-Aufruf. Beachten Sie, dass die Anzahl der Partitionen genau der Anzahl der Vergleichselemente entspricht.

  5. Setzen Sie die Datenbank in einen schreibgeschützten Modus (laufende Schreibvorgänge können zu Inkonsistenzen der Daten führen. Wenn möglich, sollten Sie die Datenbank sperren, bevor Sie den gesamten Prozess starten, in Ihrer Organisation jedoch, falls dies nicht möglich ist).

  6. Wenn die Anzahl der Partitionen mit den gewünschten Ausgabedaten ohne repartition übereinstimmt und direkt auf die Senke abläuft, können Sie, wenn nicht, die Partitionierung nach den gleichen Regeln wie in Schritt 1 durchführen.

  7. Wenn weiterhin Probleme auftreten, stellen Sie sicher, dass Sie die Spark-Speicher- und GC-Optionen ordnungsgemäß konfiguriert haben.

  8. Wenn keines der oben genannten funktioniert:

    • Erwägen Sie, Ihre Daten mit Tools wie COPY TO in ein Netzwerk zu speichern/Speicher zu verteilen, und lesen Sie sie direkt von dort aus.

      Beachten Sie, dass Standard-Datenbankdienstprogramme normalerweise ein POSIX-kompatibles Dateisystem benötigen, sodass HDFS dies normalerweise nicht tut. 

      Der Vorteil dieses Ansatzes besteht darin, dass Sie sich nicht um die Spalteneigenschaften kümmern müssen und Daten nicht schreibgeschützt sein müssen, um die Konsistenz zu gewährleisten.

    • Verwenden Sie dedizierte Bulk-Transfer-Tools wie Apache Sqoop, und ändern Sie anschließend die Daten neu.


* Nicht benutze Pseudospalten - Pseudospalten in Spark JDBC .

4
user10465355

Nach meiner Erfahrung gibt es 4 Arten von Speichereinstellungen, die einen Unterschied machen:

A) [1] Speicher zum Speichern von Daten aus Verarbeitungsgründen. VS [2] Heap Space zum Halten des Programmstapels

B) [1] Treiber-VS [2] -Ausführungsspeicher

Bisher war ich immer in der Lage, meine Spark-Jobs erfolgreich auszuführen, indem der entsprechende Speicher erhöht wurde:

A2-B1 wäre daher der Speicher, der für den Treiber zum Speichern des Programmstapels verfügbar ist. Usw.

Die Eigenschaftsnamen lauten wie folgt:

A1-B1) executor-memory

A1-B2) driver-memory

A2-B1)spark.yarn.executor.memoryOverhead

A2-B2)spark.yarn.driver.memoryOverhead

Beachten Sie, dass die Summe aller * -B1 weniger als der verfügbare Arbeitsspeicher Ihrer Mitarbeiter und die Summe aller * -B2 weniger als der Speicher Ihres Treiberknotens sein muss.

Meine Wette wäre, dass der Täter eine der fett markierten Heap-Einstellungen ist.

1
Elmar Macek

Es gab eine andere Frage von Ihnen, die hier als Duplikat weitergeleitet wurde 

 'How to avoid data skewing while reading huge datasets or tables into spark? 
  The data is not being partitioned properly. One partition is smaller while the 
  other one becomes huge on read.
  I observed that one of the partition has nearly 2million rows and 
  while inserting there is a skew in partition. '

wenn das Problem darin besteht, Daten zu behandeln, die nach dem Lesen in einem Datenrahmen partitioniert sind, haben Sie etwa mit dem Erhöhen des Werts "numPartitions" gespielt?

.option("numPartitions",50)

lowerBound, upperBound Formpartitionsschritte für generierte WHERE-Klauselausdrücke und -Partitionen bestimmen die Anzahl der Aufteilungen.

beispiel: sometable hat die Spalten-ID (wir wählen diese als partitionColumn); Der Wertebereich, den wir in der Tabelle für die Spalte -ID sehen, reicht von 1 bis 1000 und wir möchten alle Datensätze abrufen, indem Sie select * from sometable, ausführen. Wir gehen also mit lowerbound = 1 & upperbound = 1000 und numpartition = 4 vor

dadurch wird ein Datenframe von 4 Partitionen mit dem Ergebnis jeder Abfrage erstellt, indem SQL basierend auf unserem Feed erstellt wird. (lowerbound = 1 & upperbound = 1000 and numpartition = 4)

select * from sometable where ID < 250
select * from sometable where ID >= 250 and ID < 500
select * from sometable where ID >= 500 and ID < 750
select * from sometable where ID >= 750

was ist, wenn die meisten Datensätze in unserer Tabelle in den Bereich von ID(500,750) fallen. Das ist die Situation, in der Sie sich befinden.

wenn wir die Anzahl der Partitionen erhöhen, geschieht die Aufteilung sogar noch weiter und das verringert das Volumen der Datensätze in derselben Partition, aber diese ist kein guter Schuss.

Anstelle der Funkenaufteilung der partitioncolumn auf der Grundlage von Grenzen, die wir bereitstellen, können Sie die Daten gleichmäßig Aufteilen, wenn Sie daran denken, die Aufteilung selbst zu speisen. Sie müssen zu einer anderen JDBC-Methode wechseln, in der wir anstelle von (lowerbound,upperbound & numpartition) direkt Prädikate angeben können.

def jdbc(url: String, table: String, predicates: Array[String], connectionProperties: Properties): DataFrame 

Verknüpfung

0
Karthick