我正在尝试将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倍的额外时间。我知道会有一些惩罚,但这似乎太多了。
答案 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更快响应的原因。