从Postgres JDBC表中读取Spark的速度很慢

时间:2017-04-21 04:02:51

标签: postgresql apache-spark jdbc pyspark spark-dataframe

我正在尝试将PostgreSQL数据库中的大约1M行加载到Spark中。使用Spark时需要大约10秒。但是,使用psycopg2驱动程序加载相同的查询需要2s。我正在使用postgresql jdbc驱动程序版本42.0.0

alter

输出是 -

CREATE TABLE tablename (
    colname SERIAL stars 1000
);

使用psycopg2 -

def _loadFromPostGres(name):
    url_connect = "jdbc:postgresql:"+dbname
    properties = {"user": "postgres", "password": "postgres"}
    df = SparkSession.builder.getOrCreate().read.jdbc(url=url_connect, table=name, properties=properties)
    return df

df = _loadFromPostGres("""
    (SELECT "seriesId", "companyId", "userId", "score" 
    FROM user_series_game 
    WHERE "companyId"=655124304077004298) as
user_series_game""")

print measure(lambda : len(df.collect()))

输出是 -

--- 10.7214591503 seconds ---
1076131

衡量功能 -

import psycopg2
conn = psycopg2.connect(conn_string)
cur = conn.cursor()

def _exec():
    cur.execute("""(SELECT "seriesId", "companyId", "userId", "score" 
        FROM user_series_game 
        WHERE "companyId"=655124304077004298)""")
    return cur.fetchall()
print measure(lambda : len(_exec()))
cur.close()
conn.close()

请帮我找出这个问题的原因。

编辑1

我做了一些基准测试。使用Scala和JDBC -

--- 2.27961301804 seconds ---
1076131

输出是 -

def measure(func) :
    start_time = time.time()
    x = func()
    print("--- %s seconds ---" % (time.time() - start_time))
    return x

当我在Spark + Scala中执行collect()时 -

import java.sql._;
import scala.collection.mutable.ArrayBuffer;

def exec() {

val url = ("jdbc:postgresql://prod.caumccqvmegm.ap-southeast-1.rds.amazonaws.com/prod"+ 
    "?tcpKeepAlive=true&prepareThreshold=-1&binaryTransfer=true&defaultRowFetchSize=10000")

val conn = DriverManager.getConnection(url,"postgres","postgres");

val sqlText = """SELECT "seriesId", "companyId", "userId", "score" 
        FROM user_series_game 
        WHERE "companyId"=655124304077004298"""

val t0 = System.nanoTime()

val stmt = conn.prepareStatement(sqlText, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)

val rs = stmt.executeQuery()

val list = new ArrayBuffer[(Long, Long, Long, Double)]()

while (rs.next()) {
    val seriesId = rs.getLong("seriesId")
    val companyId = rs.getLong("companyId")
    val userId = rs.getLong("userId")
    val score = rs.getDouble("score")
    list.append((seriesId, companyId, userId, score))
}

val t1 = System.nanoTime()

println("Elapsed time: " + (t1 - t0) * 1e-9 + "s")

println(list.size)

rs.close()
stmt.close()
conn.close()
}

exec()

输出

Elapsed time: 1.922102285s
1143402

因此,在Python序列化中花费了4倍的额外时间。我知道会有一些惩罚,但这似乎太多了。

1 个答案:

答案 0 :(得分:2)

原因很简单,有两个原因。

首先,我将向您介绍psycopg2的工作原理。

此lib psycopg2与任何其他lib一样,可以连接到RDMS。这个lib会将查询发送到你的postgres的引擎,它会将数据返回给你。像这样直接前行。

  

Conn - >查询 - > ReturnData - > FetchData

当你使用spark时,两种方式有点不同。 Spark不像是在单个线程中运行的编程语言。它有一个分布式系统可以工作。即使您在本地计算机上运行。请参阅Spark有Driver(Master)和Workers的基本概念。

驱动程序接收执行查询到Postgres的请求,驱动程序不会为每个工作人员请求来自Postgres的信息。

如果您看到文档here,您会看到如下注释:

  

不要在大型​​群集上并行创建太多分区;否则Spark可能会使您的外部数据库系统崩溃。

此注释表示每个工作人员都有责任为您的postgres请求数据。这是开始这个​​过程的一小部分开销,但没有什么大不了的。但是这里有一个开销,将数据发送给每个工作人员。

第二点,您收集这部分代码:

print measure(lambda : len(df.collect()))

collect函数将为您的所有工作人员发送命令,以将数据发送到您的驱动程序。要存储在驱动程序的内存中,它就像一个Reduce,它会在进程的中间创建Shuffle。 Shuffle是将数据发送给其他工作人员的过程的一个步骤。在收集的情况下,每个工作人员都会将其发送给您的司机。

因此,代码的JDBC在JDBC中的步骤是:

  

(工人)Conn - > (工人)查询 - > (工人)FetchData - > (驱动程序)   请求数据 - > (工人)洗牌 - > (司机)收集

在Spark中发生的其他一些事情,比如QueryPlan,构建DataFrame和其他东西。

这就是你在简单的Python代码中比Spark更快响应的原因。