SparkアプリケーションでDynamoDBからテーブルを読み取り、何かを実行して、結果をDynamoDBに書き込みます。
現在、DynamoDBのテーブルをSparkとしてhadoopRDD
として読み取り、それをDataFrameに変換できます。ただし、正規表現を使用して値を抽出する必要がありました。 AttributeValue
。より優れた/よりエレガントな方法はありますか?AWS APIで何も見つかりませんでした。
package main.scala.util
import org.Apache.spark.sql.SparkSession
import org.Apache.spark.SparkContext
import org.Apache.spark.sql.SQLContext
import org.Apache.spark.sql.functions._
import org.Apache.spark.sql.types._
import org.Apache.spark.rdd.RDD
import scala.util.matching.Regex
import Java.util.HashMap
import com.amazonaws.services.dynamodbv2.model.AttributeValue
import org.Apache.hadoop.io.Text;
import org.Apache.hadoop.dynamodb.DynamoDBItemWritable
/* Importing DynamoDBInputFormat and DynamoDBOutputFormat */
import org.Apache.hadoop.dynamodb.read.DynamoDBInputFormat
import org.Apache.hadoop.dynamodb.write.DynamoDBOutputFormat
import org.Apache.hadoop.mapred.JobConf
import org.Apache.hadoop.io.LongWritable
object Tester {
// {S: 298905396168806365,}
def extractValue : (String => String) = (aws:String) => {
val pat_value = "\\s(.*),".r
val matcher = pat_value.findFirstMatchIn(aws)
matcher match {
case Some(number) => number.group(1).toString
case None => ""
}
}
def main(args: Array[String]) {
val spark = SparkSession.builder().getOrCreate()
val sparkContext = spark.sparkContext
import spark.implicits._
// UDF to extract Value from AttributeValue
val col_extractValue = udf(extractValue)
// Configure connection to DynamoDB
var jobConf_add = new JobConf(sparkContext.hadoopConfiguration)
jobConf_add.set("dynamodb.input.tableName", "MyTable")
jobConf_add.set("dynamodb.output.tableName", "MyTable")
jobConf_add.set("mapred.output.format.class", "org.Apache.hadoop.dynamodb.write.DynamoDBOutputFormat")
jobConf_add.set("mapred.input.format.class", "org.Apache.hadoop.dynamodb.read.DynamoDBInputFormat")
// org.Apache.spark.rdd.RDD[(org.Apache.hadoop.io.Text, org.Apache.hadoop.dynamodb.DynamoDBItemWritable)]
var hadooprdd_add = sparkContext.hadoopRDD(jobConf_add, classOf[DynamoDBInputFormat], classOf[Text], classOf[DynamoDBItemWritable])
// Convert HadoopRDD to RDD
val rdd_add: RDD[(String, String)] = hadooprdd_add.map {
case (text, dbwritable) => (dbwritable.getItem().get("PIN").toString(), dbwritable.getItem().get("Address").toString())
}
// Convert RDD to DataFrame and extract Values from AttributeValue
val df_add = rdd_add.toDF()
.withColumn("PIN", col_extractValue($"_1"))
.withColumn("Address", col_extractValue($"_2"))
.select("PIN","Address")
}
}
Stackoverflowなどの多くの回答は、 ブログ投稿 と emr-dynamodb-hadoop github のみを指します。これらのリソースはどれも実際にDynamoDBへの書き込み方法を示していません。
変換しようとしました my DataFrame
to RDD[Row]
失敗しました。
df_add.rdd.saveAsHadoopDataset(jobConf_add)
このDataFrameをDynamoDBに書き込む手順は何ですか? (overwrite
とputItem
を制御する方法を教えてくれた場合のボーナスポイント;)
注意: df_add
のスキーマはDynamoDBのMyTable
と同じです。
[〜#〜] edit [〜#〜]:私は この回答 からの推奨事項に従っており、この投稿をポイントしていますon sing Spark SQL for ETL を使用:
// Format table to DynamoDB format
val output_rdd = df_add.as[(String,String)].rdd.map(a => {
var ddbMap = new HashMap[String, AttributeValue]()
// Field PIN
var PINValue = new AttributeValue() // New AttributeValue
PINValue.setS(a._1) // Set value of Attribute as String. First element of Tuple
ddbMap.put("PIN", PINValue) // Add to HashMap
// Field Address
var AddValue = new AttributeValue() // New AttributeValue
AddValue.setS(a._2) // Set value of Attribute as String
ddbMap.put("Address", AddValue) // Add to HashMap
var item = new DynamoDBItemWritable()
item.setItem(ddbMap)
(new Text(""), item)
})
output_rdd.saveAsHadoopDataset(jobConf_add)
しかし、今私はJava.lang.ClassCastException: Java.lang.String cannot be cast to org.Apache.hadoop.io.Text
ドキュメントに従っているにもかかわらず...何か提案はありますか?
EDIT 2: sing Spark SQL for ETL :
DataFrameを取得したら、変換を実行して、DynamoDBカスタム出力形式が書き込む方法を認識しているタイプと一致するRDDを取得します。カスタム出力形式では、Textおよび
DynamoDBItemWritable
タイプを含むタプルが必要です。
これを考慮に入れて、以下のコードは、AWSブログ投稿が示唆するとおりですが、output_df
rddとして使用しない場合、saveAsHadoopDataset
は機能しません。そして今、私はException in thread "main" scala.reflect.internal.Symbols$CyclicReference: illegal cyclic reference involving object InterfaceAudience
。私はロープの端にいます!
// Format table to DynamoDB format
val output_df = df_add.map(a => {
var ddbMap = new HashMap[String, AttributeValue]()
// Field PIN
var PINValue = new AttributeValue() // New AttributeValue
PINValue.setS(a.get(0).toString()) // Set value of Attribute as String
ddbMap.put("PIN", PINValue) // Add to HashMap
// Field Address
var AddValue = new AttributeValue() // New AttributeValue
AddValue.setS(a.get(1).toString()) // Set value of Attribute as String
ddbMap.put("Address", AddValue) // Add to HashMap
var item = new DynamoDBItemWritable()
item.setItem(ddbMap)
(new Text(""), item)
})
output_df.rdd.saveAsHadoopDataset(jobConf_add)
「Spark SQL for ETL」リンクを使用していて、同じ「違法な循環参照」例外が見つかりました。その例外の解決策は非常に簡単です(ただし、計算に2日かかります)以下のように、重要な点は、データフレーム自体ではなく、データフレームのRDDでマップ機能を使用することです。
val ddbConf = new JobConf(spark.sparkContext.hadoopConfiguration)
ddbConf.set("dynamodb.output.tableName", "<myTableName>")
ddbConf.set("dynamodb.throughput.write.percent", "1.5")
ddbConf.set("mapred.input.format.class", "org.Apache.hadoop.dynamodb.read.DynamoDBInputFormat")
ddbConf.set("mapred.output.format.class", "org.Apache.hadoop.dynamodb.write.DynamoDBOutputFormat")
val df_ddb = spark.read.option("header","true").parquet("<myInputFile>")
val schema_ddb = df_ddb.dtypes
var ddbInsertFormattedRDD = df_ddb.rdd.map(a => {
val ddbMap = new HashMap[String, AttributeValue]()
for (i <- 0 to schema_ddb.length - 1) {
val value = a.get(i)
if (value != null) {
val att = new AttributeValue()
att.setS(value.toString)
ddbMap.put(schema_ddb(i)._1, att)
}
}
val item = new DynamoDBItemWritable()
item.setItem(ddbMap)
(new Text(""), item)
}
)
ddbInsertFormattedRDD.saveAsHadoopDataset(ddbConf)
Spark用のDynamoDBカスタムデータソースを作成しました。
https://github.com/audienceproject/spark-dynamodb
エレガントな機能がたくさんあります:
これは間違いなくあなたのユースケースに合うと思います。あなたがそれをチェックアウトして、フィードバックを提供することができれば私達は大好きです。
これはやや簡単な作業例です。
Hadoop RDDを使用したKinesis StreamからDynamoDBへの書き込みの例:-
Hadoop RDDを使用してDynamoDBから読み取る場合、およびspark SQLを正規表現なしで使用する場合。
val ddbConf = new JobConf(spark.sparkContext.hadoopConfiguration)
//ddbConf.set("dynamodb.output.tableName", "student")
ddbConf.set("dynamodb.input.tableName", "student")
ddbConf.set("dynamodb.throughput.write.percent", "1.5")
ddbConf.set("dynamodb.endpoint", "dynamodb.us-east-1.amazonaws.com")
ddbConf.set("dynamodb.regionid", "us-east-1")
ddbConf.set("dynamodb.servicename", "dynamodb")
ddbConf.set("dynamodb.throughput.read", "1")
ddbConf.set("dynamodb.throughput.read.percent", "1")
ddbConf.set("mapred.input.format.class", "org.Apache.hadoop.dynamodb.read.DynamoDBInputFormat")
ddbConf.set("mapred.output.format.class", "org.Apache.hadoop.dynamodb.write.DynamoDBOutputFormat")
//ddbConf.set("dynamodb.awsAccessKeyId", credentials.getAWSAccessKeyId)
//ddbConf.set("dynamodb.awsSecretAccessKey", credentials.getAWSSecretKey)
val data = spark.sparkContext.hadoopRDD(ddbConf, classOf[DynamoDBInputFormat], classOf[Text], classOf[DynamoDBItemWritable])
val simple2: RDD[(String)] = data.map { case (text, dbwritable) => (dbwritable.toString)}
spark.read.json(simple2).registerTempTable("gooddata")
spark.sql("select replace(replace(split(cast(address as string),',')[0],']',''),'[','') as housenumber from gooddata").show(false)