Uh oh!
There was an error while loading. Please reload this page.
[SPARK-19163][PYTHON][SQL] Delay _judf initialization to the __call__ - #16536
[SPARK-19163][PYTHON][SQL] Delay _judf initialization to the __call__#16536zero323 wants to merge 8 commits into
Conversation
SparkQA
commented
Jan 10, 2017
Test build #71163 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 12, 2017
Test build #71254 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 13, 2017
Test build #71351 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 13, 2017
Test build #71350 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 19, 2017
Test build #71679 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 20, 2017
Test build #71688 has finished for PR 16536 at commit
|
rdblue
commented
Jan 20, 2017
+1 Looks good to me. |
holdenk
left a comment
There was a problem hiding this comment.
Thanks for working on this! I think this is going to be useful for Python UDF libraries. I've got a few questions - let me know what your thoughts are :)
There was a problem hiding this comment.
Maybe add a comment explaining the purposes of this, just for future readers of the code.
There was a problem hiding this comment.
This seems like a good test but maybe a bit too focused on testing the implementation specifics?
Maybe it might more sense to also have a test which verifies creating a UDF doesn't create a SparkSession since that is the intended purposes (we don't really care about delaying the initialization of _judfy that much per-se but we do care about verifying that we don't eagerly create the SparkSession on import). What do you think?
There was a problem hiding this comment.
I thought about it but I have this impression, maybe incorrect, that we avoid creating new contexts to keep total execution time manageable. If you think this justifies a separate TestCase I am more than fine with that (SPARK-19224 and [PYSPARK] Python tests organization , right?).
If not, we could mock this, and put assert on the number of calls.
There was a problem hiding this comment.
I think a seperate test case and would able to be pretty light weight since it doesn't need to create a SparkContext or anything which traditionally takes longer to set up. What do you think?
There was a problem hiding this comment.
@holdenk Separate case it is. As long as implementation is correct an overhead is negligible.
There was a problem hiding this comment.
Let's keep these tests, to make sure that _judf is initialized when necessary.
There was a problem hiding this comment.
there is a assertIsInstance function that could simplify this.
There was a problem hiding this comment.
So there isn't any lock around this - I suspect we aren't too likely to have concurrent calls to this - but just to be safe we should maybe think through what would happen if this is does happen (and then leave a comment about it)?
There was a problem hiding this comment.
Could you elaborate a bit? I am not sure if I understand the issue.
Assignment is atomic (so we don't have to worry about corruption), for any practical purpose operation is idempotent (we can return expressions using different Java objects but as far as I am concerned this is just a detail of implementation), access to Py4J is thread safe and as far as I remember function registries are synchronized. I there any issue i missed here?
Thanks for looking into this.
There was a problem hiding this comment.
I think @holdenk's concern is that this would allow concurrent calls to _create_udf. That would create two UserDefinedPythonFunction objects, but I don't see anything on the Scala side that is concerning about that.
There was a problem hiding this comment.
@rdblue I get this part, and this is a possible scenario. Question is if this justifies preventive lock. As far as I am aware there should be no correctness issues here. SparkSession already locks during initialization so we are safe there.
There was a problem hiding this comment.
Yeah, I don't think it should require a lock. I think concurrent calls are very unlikely and safe.
There was a problem hiding this comment.
I stress tested this a bit and I haven't found any abnormalities but I found a small problem with __call__ on the way. Fixed now.
There was a problem hiding this comment.
Ok, I'd maybe just leave a comment saying that we've left out the lock since double creation is both unlikely and OK.
SparkQA
commented
Jan 26, 2017
Test build #72021 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 26, 2017
Test build #72034 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 26, 2017
Test build #72039 has finished for PR 16536 at commit
|
There was a problem hiding this comment.
@holdenk I believe that for a full test udf would have to create SparkContext. But mock is cheap.
There was a problem hiding this comment.
You can create a testcase without the Spark base and verify that creating a UDF doesn't create a SparkContext. This does not require making a SparkContext.
There was a problem hiding this comment.
@holdenk Do you mean something like SparkContext._active_spark_context is None? Then we need to make sure it is tear down if it was initialized after all, right? Isn't mocking cleaner?
There was a problem hiding this comment.
classUDFInitializationTestCase(unittest.TestCase):
deftearDown(self):
ifSparkSession._instantiatedSessionisnotNone:
SparkSession._instantiatedSession.stop()
ifSparkContext._active_spark_contextisnotNone:
SparkContext._active_spark_contex.stop()
deftest_udf_context_access(self):
frompyspark.sql.functionsimportUserDefinedFunctionf=UserDefinedFunction(lambdax: x, StringType())
self.assertIsNone(SparkContext._active_spark_context)
self.assertIsNone(SparkSession._instantiatedSession)
There was a problem hiding this comment.
And add a separate test case checking SparkContext and SparkSession state.
SparkQA
commented
Jan 30, 2017
Test build #72170 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 30, 2017
Test build #72172 has finished for PR 16536 at commit
|
SparkQA
commented
Jan 30, 2017
Test build #72173 has finished for PR 16536 at commit
|
holdenk
commented
Jan 30, 2017
The changes look good to me, I'll take a quick pass at the formatting to make sure - but otherwise I'll try and merge this tomorrow :) |
holdenk
left a comment
There was a problem hiding this comment.
Ok it looks good, just one minor comment about having getOrCreate which acquires a lock in the hot path for __call__. Thanks for adding the tests :)
There was a problem hiding this comment.
So by switching this to getOrCreate we put a lock acquisition in the path of __call__ which is maybe not ideal. We could maybe fix this by getting _judf first (e.g. judf = self._judf)? (Although it should be a mostly uncontended lock so it shouldn't be that bad, but if we ended up having a multi-threaded PySpark DataFrame UDF application this could maybe degrade a little bit).
There was a problem hiding this comment.
Though I am not sure if it really matters here. If _instantiatedContext is not None we'll do the same thing, otherwise we fall back to initialization in _judf.
zero323
commented
Feb 1, 2017
@holdenk I have one more suggestion. Shouldn't we replace def_create_judf(self):
frompyspark.sqlimportSparkSessionsc=SparkContext.getOrCreate()
spark=SparkSession.builder.getOrCreate()with def_create_judf(self):
frompyspark.sqlimportSparkSessionspark=SparkSession.builder.getOrCreate()
sc=spark.sparkContextI left it as is but I think it could be cleaner. |
SparkQA
commented
Feb 1, 2017
Test build #72218 has finished for PR 16536 at commit
|
SparkQA
commented
Feb 1, 2017
Test build #72219 has finished for PR 16536 at commit
|
holdenk
commented
Feb 1, 2017
@zero323 that sounds like a good improvement. |
zero323
commented
Feb 1, 2017
@holdenk Done :) |
holdenk
commented
Feb 1, 2017
Great, I'll wait for jenkins then :) |
SparkQA
commented
Feb 1, 2017
Test build #72222 has finished for PR 16536 at commit
|
holdenk
commented
Feb 1, 2017
Going to go ahead and merge. Still need to sort out the JIRA permissions so will take a bit for me to get that updated for you. |
zero323
commented
Feb 1, 2017
Thanks a bunch @holdenk |
## What changes were proposed in this pull request? Defer `UserDefinedFunction._judf` initialization to the first call. This prevents unintended `SparkSession` initialization. This allows users to define and import UDF without creating a context / session as a side effect. [SPARK-19163](https://issues.apache.org/jira/browse/SPARK-19163) ## How was this patch tested? Unit tests. Author: zero323 <zero323@users.noreply.github.com> Closesapache#16536 from zero323/SPARK-19163.
What changes were proposed in this pull request?
Defer
UserDefinedFunction._judfinitialization to the first call. This prevents unintendedSparkSessioninitialization. This allows users to define and import UDF without creating a context / session as a side effect.SPARK-19163
How was this patch tested?
Unit tests.