Uh oh!
There was an error while loading. Please reload this page.
[SPARK-16921][PYSPARK] RDD/DataFrame persist()/cache() should return Python context managers - #14579
[SPARK-16921][PYSPARK] RDD/DataFrame persist()/cache() should return Python context managers#14579MLnick wants to merge 1 commit into
Conversation
SparkQA
commented
Aug 10, 2016
Test build #63520 has finished for PR 14579 at commit
|
MLnick
commented
Aug 10, 2016
Note: withrdd.map(lambdax: x) asx:
...Clearly this doesn't make a lot of sense. However, I looked at the 2 options of (a) a separate context manager wrapper class returned by The problem with (a) is that The problem with (b) is that the special method So, if we want to avoid that, the only option I see is a variant of (a) above - adding a withcached(rdd) asx:
x.count()This is less "elegant" but more explicit. Any other smart ideas for handling option (b) above, please do shout! |
nchammas
commented
Aug 10, 2016
Thanks @MLnick for taking this on and for breaking down what you've found so far. I took a look through Have you taken a look at that? |
MLnick
commented
Aug 10, 2016
@nchammas I looked at the As far as I can see for the |
Ah, you're right. So if we want to avoid needing magic methods in the main RDD/DataFrame classes and avoid needing a separate utility method like
What do you think of that? |
nchammas
commented
Aug 10, 2016
None of our options seems great, but if I had to rank them I would say:
Adding new internal classes for this use-case honestly seems a bit heavy-handed to me, so if we are against that then I would lean towards not doing anything. |
rxin
commented
Aug 10, 2016
cc @davies |
holdenk
commented
Aug 10, 2016
One minor thing to keep in mind - the subclassing of RDD approach could cause us to miss out on pipelining if the RDD was used again after it was unpersisted - but I think that is a relatively minor issue. On the whole I think modify base rdd and dataframe classes (option A / option 4) which is the one @MLnick has implemented here is probably one of the more reasonable options - the But if there is a better way to do this I'd be excited to find out as well :) |
nchammas
commented
Aug 10, 2016
How so? Wouldn't |
holdenk
commented
Aug 10, 2016
@nchammas so if we go with the subclassing approach but keep the current cache/persist interface (e.g. no special utility function) a user could easily write something like: I don't believe |
| :py:meth:`cache` can be used in a 'with' statement. The RDD will be automatically | ||
| unpersisted once the 'with' block is exited. Note however that any actions on the RDD | ||
| that require the RDD to be cached, should be invoked inside the 'with' block; otherwise, | ||
| caching will have no effect. |
There was a problem hiding this comment.
Super minor documentation suggestion - but I was thinking maybe a version changed directive could be helpful to call out that its new functionality (both in RDD and DF)?
There was a problem hiding this comment.
Agreed, especially since this is technically a new Public API that we are potentially committing to for the life of the 2.x line.
nchammas
commented
Aug 10, 2016
Sorry, you're right, But I'm not seeing the issue with the example you posted. Reformatting for clarity: magic=rdd.persist()
withmagicasawesome:
awesome.count()
magic.map(lambdax: x+1)Are you saying
|
MLnick
commented
Aug 10, 2016
yeah it would break pipelining - I don't think it will necessarily throw an error though. e.g. So I think chaining will work, but the pipelined RDD thinks mapped2 is the 1st transformation, while it is actually the 2nd. I think this will just be an efficiency issue rather than a correctness issue however. We could possibly work around it with some type checking etc but it then starts to feel like adding more complexity than the feature is worth... |
nchammas
commented
Aug 10, 2016
Ah, I see. I don't fully understand how
Agreed. At this point, actually, I'm beginning to feel this feature is not worth it. Context managers seem to work best when the objects they're working on have clear open/close-style semantics. File handles, network connections, and the like fit this pattern well. In fact, the doc for
RDDs and DataFrames, on the other hand, don't have a simple open/close or |
holdenk
commented
Aug 11, 2016
Right I wouldn't expect it to error with subclassing - just not pipeline successfully - but only in a very long shot corner case. I think the try/finally with persistance is not an uncommon pattern (we have something similar happen frequently inside of Spark ML/mllib but its in Scala code). |
MLnick
commented
Aug 11, 2016
@nchammas a utility method (e.g. |
MLnick
commented
Aug 11, 2016
After looking at it and considering all the above, I would say the options are (1) do nothing; or (2) if we want to support this use case, then we implement a single utility method (I would say called Even though we could almost achieve things with subclassing, we do sort of break something and add too much risk/complexity vs reward of the feature IMHO. |
nchammas
commented
Aug 11, 2016
@MLnick - Couldn't we also create a scenario (like @holdenk did earlier) where a user does something like this? persisted_rdd=persisted(rdd)
persisted_rdd.map(...).filter(...).count()This would break pipelining too, no? And I think the expectation would be for it not to break pipelining, because existing common context managers in Python don't have a requirement that they must be used in a For example, |
holdenk
commented
Aug 11, 2016
@nchammas to be clear - subclassing only breaks pipelining if the persisted_rdd is later unpersisted (e.g. used with a |
nchammas
commented
Aug 11, 2016
Hmm, OK I see. (Apologies, I don't understand what pipelined RDDs are for, so the examples are going a bit over my head. 😅) |
holdenk
commented
Aug 11, 2016
Sure - so at a bit of a high level and not like exactly on point - copying data out of the Python back to the JVM is kind of slow so if we have multiple python operations we can put together in the same task then we try and do this. Since caching is handled by storing the data inside of the JVM a cached RDD can't be pipelined since we need to copy the result to the JVM. You can see the details in the PipelinedRDD in rdd.py. |
nchammas
commented
Aug 11, 2016
Thanks for the quick overview. That's pretty straightforward, actually! I'll take a look at |
MLnick
commented
Aug 11, 2016
@nchammas to answer your question above (#14579 (comment)) - in short no. The semantics of the utility method will be the same as for the classpersisted():
def__init__(self, thing):
self.thing=thingdef__enter__(self):
returnself.thingdef__exit__(self, *exc_info):
self.thing.unpersist()If someone tries to do The "file-like" version is what is currently implemented in this PR, and it works if |
| self._id = jrdd.id() | ||
| self.partitioner = None | ||
| def __enter__(self): |
There was a problem hiding this comment.
Is it reasonable just to raise an error saying that the context manager is meant to work only with cached RDD's (Dataframes) if self.is_cached is not set to True to solve problems such as the usage of with rdd.map(lambda x: x) as x: ?
There was a problem hiding this comment.
We could do it - but users can still then do with rdd.cache().map(...) as x: and it would be valid. So it doesn't fully solve the issue.
There was a problem hiding this comment.
Is that true? Doesn't it call __enter__ on the instance of rdd.cache().map(...) where is_cached is set to False?
Quick verification:
def__enter__(self)
ifself.is_cached:
returnselfelse:
raiseValueError("r")
withrdd.cache().map(lambdax: x) ast:
passraises a ValueError
There was a problem hiding this comment.
Thats an interesting approach @MechCoder I think that could be a way to clarify how to expect to use the context manager to users.
There was a problem hiding this comment.
yeas, also known as the "If you don't know what to do; raise an Error" approach :p
There was a problem hiding this comment.
hmmm, yes this does happen to work, because most operations boil down to something like mapPartitions which creates a new PipelineRDD which is not cached, or a new RDD which is again not cached.
I think it will work for DataFrame too for similar reason - most operations return a new DataFrame instance.
MLnick
commented
Aug 25, 2016
@nchammas@holdenk@davies@rxin how about the approach of @MechCoder in #14579 (comment)? I think this will work well, so we could raise an error to prevent (almost all I think) usages outside of the intended pattern of |
holdenk
commented
Aug 25, 2016
I like it personally - if no one has a good reason why not it seems like a very reasonable approach. |
nchammas
commented
Aug 25, 2016
Looks good to me. 👍 |
holdenk
commented
Oct 1, 2016
@MLnick still interested in updating this? (Just looking over the older Python PRs) :) |
MLnick
commented
Oct 3, 2016
Yup! just been snowed under :( but will update with the approach above asap. |
holdenk
commented
Nov 1, 2016
Just pinging to see how its going? |
MLnick
commented
Nov 2, 2016
I hope to get to this soon - It's just the test cases that I need to get to also! |
holdenk
commented
Nov 26, 2016
just a gentle ping - would be cool to add this for 2.1 we have the time :) |
HyukjinKwon
commented
Feb 9, 2017
Is there any reason why it is not merged yet? I personally like this too. |
holdenk
commented
Feb 24, 2017
Do you have time to update this @MLnick or maybe would it be OK if someone else made an updated PR based on this? It would be a nice feature to have for 2.2 :) |
HyukjinKwon
commented
May 11, 2017
gentle ping. |
ueshin
commented
Jun 20, 2017
@MLnick Hi, are you still working on this? If so, could you fix conflicts and update please? |
holdenk
commented
Jul 2, 2017
@MLnick - or if you don't have a chance would it be ok for us to find someone (perhaps someone new to the project) to take this over and bring it to the finish line? |
MLnick
commented
Aug 2, 2017
Hey all - sorry I haven't been able to focus on this. It shouldn't be tough to do, but it will need some tests. If we can find someone who wants to take it over I think it makes a decent starter task :) |
## What changes were proposed in this pull request? This PR proposes to close stale PRs, mostly the same instances with apache#18017Closesapache#14085 - [SPARK-16408][SQL] SparkSQL Added file get Exception: is a directory … Closesapache#14239 - [SPARK-16593] [CORE] [WIP] Provide a pre-fetch mechanism to accelerate shuffle stage. Closesapache#14567 - [SPARK-16992][PYSPARK] Python Pep8 formatting and import reorganisation Closesapache#14579 - [SPARK-16921][PYSPARK] RDD/DataFrame persist()/cache() should return Python context managers Closesapache#14601 - [SPARK-13979][Core] Killed executor is re spawned without AWS key… Closesapache#14830 - [SPARK-16992][PYSPARK][DOCS] import sort and autopep8 on Pyspark examples Closesapache#14963 - [SPARK-16992][PYSPARK] Virtualenv for Pylint and pep8 in lint-python Closesapache#15227 - [SPARK-17655][SQL]Remove unused variables declarations and definations in a WholeStageCodeGened stage Closesapache#15240 - [SPARK-17556] [CORE] [SQL] Executor side broadcast for broadcast joins Closesapache#15405 - [SPARK-15917][CORE] Added support for number of executors in Standalone [WIP] Closesapache#16099 - [SPARK-18665][SQL] set statement state to "ERROR" after user cancel job Closesapache#16445 - [SPARK-19043][SQL]Make SparkSQLSessionManager more configurable Closesapache#16618 - [SPARK-14409][ML][WIP] Add RankingEvaluator Closesapache#16766 - [SPARK-19426][SQL] Custom coalesce for Dataset Closesapache#16832 - [SPARK-19490][SQL] ignore case sensitivity when filtering hive partition columns Closesapache#17052 - [SPARK-19690][SS] Join a streaming DataFrame with a batch DataFrame which has an aggregation may not work Closesapache#17267 - [SPARK-19926][PYSPARK] Make pyspark exception more user-friendly Closesapache#17371 - [SPARK-19903][PYSPARK][SS] window operator miss the `watermark` metadata of time column Closesapache#17401 - [SPARK-18364][YARN] Expose metrics for YarnShuffleService Closesapache#17519 - [SPARK-15352][Doc] follow-up: add configuration docs for topology-aware block replication Closesapache#17530 - [SPARK-5158] Access kerberized HDFS from Spark standalone Closesapache#17854 - [SPARK-20564][Deploy] Reduce massive executor failures when executor count is large (>2000) Closesapache#17979 - [SPARK-19320][MESOS][WIP]allow specifying a hard limit on number of gpus required in each spark executor when running on mesos Closesapache#18127 - [SPARK-6628][SQL][Branch-2.1] Fix ClassCastException when executing sql statement 'insert into' on hbase table Closesapache#18236 - [SPARK-21015] Check field name is not null and empty in GenericRowWit… Closesapache#18269 - [SPARK-21056][SQL] Use at most one spark job to list files in InMemoryFileIndex Closesapache#18328 - [SPARK-21121][SQL] Support changing storage level via the spark.sql.inMemoryColumnarStorage.level variable Closesapache#18354 - [SPARK-18016][SQL][CATALYST][BRANCH-2.1] Code Generation: Constant Pool Limit - Class Splitting Closesapache#18383 - [SPARK-21167][SS] Set kafka clientId while fetch messages Closesapache#18414 - [SPARK-21169] [core] Make sure to update application status to RUNNING if executors are accepted and RUNNING after recovery Closesapache#18432 - resolve com.esotericsoftware.kryo.KryoException Closesapache#18490 - [SPARK-21269][Core][WIP] Fix FetchFailedException when enable maxReqSizeShuffleToMem and KryoSerializer Closesapache#18585 - SPARK-21359 Closesapache#18609 - Spark SQL merge small files to big files Update InsertIntoHiveTable.scala Added: Closesapache#18308 - [SPARK-21099][Spark Core] INFO Log Message Using Incorrect Executor I… Closesapache#18599 - [SPARK-21372] spark writes one log file even I set the number of spark_rotate_log to 0 Closesapache#18619 - [SPARK-21397][BUILD]Maven shade plugin adding dependency-reduced-pom.xml to … Closesapache#18667 - Fix the simpleString used in error messages Closesapache#18782 - Branch 2.1 Added: Closesapache#17694 - [SPARK-12717][PYSPARK] Resolving race condition with pyspark broadcasts when using multiple threads Added: Closesapache#16456 - [SPARK-18994] clean up the local directories for application in future by annother thread Closesapache#18683 - [SPARK-21474][CORE] Make number of parallel fetches from a reducer configurable Closesapache#18690 - [SPARK-21334][CORE] Add metrics reporting service to External Shuffle Server Added: Closesapache#18827 - Merge pull request 1 from apache/master ## How was this patch tested? N/A Author: hyukjinkwon <gurwls223@gmail.com> Closesapache#18780 from HyukjinKwon/close-prs.
JIRA: https://issues.apache.org/jira/browse/SPARK-16921
Context managers are a natural way to capture closely related setup and teardown code in Python. It can be useful to apply this pattern to persisting/unpersisting RDDs and DataFrames.
This PR makes RDDs and DataFrames implement the context manager
__enter__and__exit__functions, allowing code such as:How was this patch tested?
New doc tests.