web-dev-qa-db-ja.com

JDBCソースからデータを移行するときに、パーティションを最適化する方法は?

PostgreSQLテーブルのテーブルからHDFSのHiveテーブルにデータを移動しようとしています。そのために、次のコードを思いつきました。

  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")

データは、prtn_String_columns: source_system_name, period_year, period_numに基づいて動的にパーティション分割されたHiveテーブルに挿入されます

使用するSpark-submit:

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

次のエラーメッセージがエグゼキュータログに生成されます。

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)

以下のように、指定された数のパーティションで読み取りが正しく実行されていることがログでわかります。

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

以下は段階的なエグゼキューターの状態です: enter image description here

enter image description here

enter image description here

enter image description here

データは適切に分割されていません。 1つのパーティションは小さく、もう1つのパーティションは巨大になります。ここにスキュー問題があります。データをHiveテーブルに挿入している間、ジョブは次の行で失敗します:spark.sql(s"INSERT OVERWRITE TABLE schema.hivetable PARTITION(${prtn_String_columns}) select * from preparedDF")が、データスキューの問題が原因で発生していることを理解しています。

私はエグゼキューターの数を増やし、エグゼキューターのメモリとドライバーのメモリを増やし、データフレームをHiveテーブルに保存する代わりにcsvファイルとして保存しようとしましたが、例外による実行には影響しませんでした。

Java.lang.OutOfMemoryError: GC overhead limit exceeded

コードに修正が必要なものはありますか?誰でも私にこの問題を解決する方法を教えてもらえますか?

9
Metadata
  1. 入力データの量とクラスターリソースを考慮して、必要なパーティションの数を決定します。経験則として、厳密に必要でない限り、パーティションの入力は1GB未満に保つことをお勧めします。ブロックサイズの制限よりも厳密に小さい。

    前に述べた さまざまな投稿(5〜70)で使用する1TBのデータ値を移行すると、スムーズなプロセスを確実にするために低くなる可能性があります。

    さらにrepartitioningを必要としない値を使用してください。

  2. あなたのデータを知っています。

    データセットで使用可能な列を分析して、カーディナリティが高く、均一な分布の列が必要な数のパーティションに分散されているかどうかを判断します。これらはインポートプロセスの良い候補です。さらに、値の正確な範囲を決定する必要があります。

    中心性と歪度の異なる集計、ヒストグラム、およびキーごとの基本的なカウントは、優れた探索ツールです。この部分では、データをSparkにフェッチするのではなく、データベースで直接分析することをお勧めします。

    RDBMSによっては、width_bucket(PostgreSQL、Oracle)または同等の関数を使用して、データがpartitionColumnlowerBoundupperBoundnumPartitonsでロードされた後のSpark 。

    s"""(SELECT width_bucket($partitionColum, $lowerBound, $upperBound, $numPartitons) AS bucket, COUNT(*)
    FROM t
    GROUP BY bucket) as tmp)"""
    
  3. 上記の基準を満たす列がない場合は、次のことを考慮してください。

    • カスタムのものを作成し、それを介して公開します。ビュー。複数の独立した列のハッシュは、通常、適切な候補です。ここで使用できる関数(OracleではDBMS_CRYPTO、PostgreSQLではpgcrypto)を確認するには、データベースのマニュアルを参照してください*。
    • 一緒に取られた一連の独立した列を使用すると、十分に高いカーディナリティが提供されます。

      オプションで、パーティション分割されたHiveテーブルに書き込む場合は、Hiveパーティション分割列を含めることを検討する必要があります。後で生成されるファイルの数が制限される場合があります。

  4. パーティション化引数を準備する

    • 前の手順で選択または作成された列が数値の場合は、partitionColumnとして直接提供し、以前に決定された範囲値を使用してlowerBoundupperBoundを入力します。

      バインドされた値がデータのプロパティを反映していない場合(lowerBoundの場合はmin(col)upperBoundの場合はmax(col))、データが大きく歪む可能性があるため、慎重にスレッド化してください。最悪のシナリオでは、境界がデータの範囲をカバーしない場合、すべてのレコードが単一のマシンによってフェッチされるため、パーティション化をまったく行わないのと同じです。

    • 前の手順で選択した列がカテゴリ型または列のセットである場合、データを完全にカバーする相互に排他的な述語のリストを、次の形式で生成します。 SQL where句で使用できます。

      たとえば、値が{a1a2a3}のA列と、値が{b1b2b3}のB列がある場合:

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

      条件が重複しておらず、すべての組み合わせがカバーされていることを再確認してください。これらの条件が満たされない場合は、それぞれ重複または欠落したレコードになります。

      predicates呼び出しにjdbc引数としてデータを渡します。パーティションの数は、述語の数と完全に同じになることに注意してください。

  5. データベースを読み取り専用モードにします(進行中の書き込みはデータの不整合を引き起こす可能性があります。可能であれば、プロセス全体を開始する前にデータベースをロックする必要がありますが、それができない場合は、組織内で)。

  6. パーティションの数がrepartitionなしで目的の出力負荷データと一致し、シンクに直接ダンプする場合、そうでない場合は、手順1と同じルールに従って再パーティションを試行できます。

  7. それでも問題が発生する場合は、SparkメモリおよびGCオプションが正しく構成されていることを確認してください。

  8. 上記のいずれも機能しない場合:

    • COPY TOなどのツールを使用してデータをネットワークにダンプしたりストレージを分散したりして、そこから直接読み取ることを検討してください。

      または標準のデータベースユーティリティでは、通常、POSIX準拠のファイルシステムが必要になるため、HDFSでは通常は必要ありません。

      このアプローチの利点は、列のプロパティを気にする必要がなく、一貫性を確保するためにデータを読み取り専用モードにする必要がないことです。

    • Apache Sqoopなどの専用の一括転送ツールを使用し、後でデータを再形成します。


*疑似列を使用しない疑似列- 疑似列Spark JDBC

4
user10465355

私の経験では、違いを生む4種類のメモリ設定があります。

A)[1]処理上の理由でデータを格納するためのメモリVS [2]プログラムスタックを保持するためのヒープスペース

B)[1]ドライバーVS [2]エグゼキューターメモリ

これまでは、適切な種類のメモリを増やすことで、常にSparkジョブを正常に実行することができました。

したがって、A2-B1は、プログラムスタックを保持するためにドライバーで利用可能なメモリになります。等。

プロパティ名は次のとおりです。

A1-B1)executor-memory

A1-B2)driver-memory

A2-B1)spark.yarn.executor.memoryOverhead

A2-B2)spark.yarn.driver.memoryOverhead

すべての* -B1の合計はワーカーで使用可能なメモリよりも少なく、すべての* -B2の合計はドライバーノードのメモリよりも少ない必要があることに注意してください。

私の賭けは、犯人が大胆にマークされたヒープ設定の1つであることです。

1
Elmar Macek

重複としてここにルーティングされた別の質問がありました

_ '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. '
_

問題が読み取り後にデータフレームに分割されたデータを処理することである場合、「numPartitions」値の増加を試しましたか?

_.option("numPartitions",50)
_

_lowerBound, upperBound_フォームパーティションは、生成されたWHERE句の式に対してストライドし、numpartitionsは分割数を決定します。

たとえば、sometableにcolumn-ID(partitionColumnとして選択)があるとします。 column -IDのテーブルに表示される値の範囲は1〜1000であり、_select * from sometable_を実行してすべてのレコードを取得したいので、lowerbound = 1&upperbound = 1000およびnumpartition = 4

これは、フィードに基づいてSQLを構築することにより、各クエリの結果を含む4パーティションのデータフレームを生成します_(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
_

テーブル内のほとんどのレコードがID(500,750)の範囲内にある場合はどうなりますか。それはあなたがしている状況です。

numpartitionを増やすと、分割はさらに進み、同じパーティション内のレコードの量が減りますが、これは良いショットではありません。

spark提供する境界に基づいてpartitioncolumnを分割するのではなく、分割を自分でフィードしてデータを均等に分割できるようにする場合は、データを均等に分割できます。別のものに切り替える必要があります。 _(lowerbound,upperbound & numpartition)_の代わりに述語を直接提供できるJDBCメソッド。

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

リンク

0
Karthick