Parse-sdk-ios-osx: LDS Performance Improvements

Created on 17 Aug 2015  ·  101Comments  ·  Source: parse-community/Parse-SDK-iOS-OSX

This is a master issue for all things related to local datastore performance improvements.

  • [x] Make LDS stop checkpointing and making copies of objects for mutable containers (#102)
  • [ ] Make LDS query matching more efficient by not loading every object into memory for a specified pin.
  • [ ] Make LDS use PFObjectState and PFUserState instead of PFObject and PFUser, to reduce the amount of locking required for every LDS operation.
  • [ ] Improve LDS PFRelation tracking.
  • [ ] Update LDS database schema to allow for query matching in the database layer itself.

For anyone reaching this issue as a result of currently bad LDS performance here's our current recommendations:

  • Keep your object schemas as small as possible - the faster it is to parse them from JSON, the better.
  • Relations are currently inefficient. If you can avoid them, do so.
  • Minimize usage of any values of the following classes:

    • NSArray

    • NSDictionary

    • PFACL

    • PFGeoPoint

  • Whenever possible, use smaller pins. Because LDS loads all objects in a single pin before evaluating a query, the smaller the pin is, the better.
Discussion Enhancement Help Wanted Stale

Most helpful comment

Maybe it would be beneficial to look at an integration with realm.io?

All 101 comments

+1, LDS performance needs lots of work. I'm glad you're finally publicly acknowledging this. See my angry bug report from April 2015: https://developers.facebook.com/bugs/1575754175998187

+1: 2.3 sec to get one row on a table of 800 objects... Ouch.

:+1:

Hi

Sorry to came back. Do you think one of these point can be associate to a milestone?

Maybe not clean, but a PFQuery with a whereKey pointing to a String can be filtered direct from the DB (json), without unwrap.

Just with this, Query time will be 99% smaller.

Just my 2 cents

Hey @FreudGit, absolutely!
I've just attached a PR to this that implements the first bullet point in that list.
From our testing - it should improve performance significantly.

But you are right - we should attach more to milestones. Making it happen...

WOW, thanks for the fast reply.

PR? This was a bit to technical for me. But i'm happy if we can target and see better performance in the next release\weeks. :+1:

PR is a shorthand for Pull Request

Since removing mutable containers functionality can break few flows for people, we are sitting on it for a little bit longer than next release. I think we are going to ship it with 1.8.4

Thanks again @nlutsenko.
Wish 1.8.4 will come soon or that you will be ok to move to 1.8.3 milestone. Let me know if\when there is a way to try.

(completely off-topic)= how do you manage the multiple version(android, etc). Do you sync revision with other team(Android, .net, etc? Just curious!

@FreudGit, 1.8.4 is coming very very soon after 1.8.4. A small tip is to look at the release schedule of 3 previous releases, it might get you an idea of how we try to release at the current moment.

So far we don't sync versions between Android/.NET/iOS(OSX), since we have separate dependencies.
If we are releasing a new feature that is cross-platform, say new Sessions APIs that we released at F8 - we do the release at the same time. Most of the time though - we have separate release schedules.

Thanks(still curious). Are the dev of each language share code\need to have the more similar structure as possible?

Not sure about the last question...
We don't share any code currently, but the APIs/class structure and architecture are unified and share a lot of the same principles and patterns, though adapted to best platform practices.

Sorry for my bad english. And have a nice day.

Just curious if, for example, Android LDS have the same performance problem because very similair coding approach.

Dont want to bug anymore, no need to reply and no more question!

Got you. I think the answer is yes - we share the same architecture and approaches for LDS, so say removing mutable containers on Android will also improve performance quite a lot!
No problem, please bug us, and I am always happy to help! :grin:

@nlutsenko Are 1.8.4 still a target for this ? :)

I wish my answer was yes.
We are aiming for 1.8.4 for early/mid next week, so looks like 1.8.5 is the target for this.

1.8.5 does contain good performance on LDS pinning and fetching?

And, LDS performance issue persists on other parse SDKs like Android, OSX etc.

Any update? Does anyone have tips for workarounds?

I am glad to know that iOS sdk 1.9.0 is finally released so will it solve LDS issues?

There are improvements for LDS, yes.
Solve - possibly, but #102 is marked for 1.10.0 - which will have a much bigger impact on the performance compared to 1.9.0.

so 1.9.0 will solve slow pinning issue in LDS? And 1.10.0 will solve mutable container isssue right?

I think i should update to 1.9.0

1.10.0 will remove mutable containers, which degrade LDS performance by a significant portion of time.

So for slow pinning 1.9.0 is perfect solution for now?

And what are the mutable containers i don't find anything about it in docs.

97 - has the information on what's changing.

The latest framework version always has the latest and most up-to-date implementations of everything.

Can we get an update on the Android version? When can we expect the LOS performance issue to be solved on Android?

cc @grantland for Android version.

@nlutsenko What are the next checkbox to be solved? :)

Are there any updates with this - just want to prepare since it's really killing the performance of my app.

@kevflynn Mutable containers is getting removed in the next major release of the SDK, and that should provide a noticeable speedup for most cases.

Internally, the team has discussed other improvements, but it's going to be extremely difficult to actually implement most of them in practice due to backwards compatibility concerns.

Of course, we always accept community feedback on features like this, so if you have any suggestions on how we could improve performance, feel free to leave any ideas in this thread!

@richardross will this be in the next release for both ios and android? Is
there a thread for lds performance problems on Android?
On Nov 3, 2015 8:06 PM, "Richard Ross" [email protected] wrote:

@kevflynn https://github.com/kevflynn Mutable containers is getting
removed in the next major release of the SDK, and that should provide a
noticeable speedup for most cases.

Internally, the team has discussed other improvements, but it's going to
be extremely difficult to actually implement most of them in practice due
to backwards compatibility concerns.

Of course, we always accept community feedback on features like this, so
if you have any suggestions on how we could improve performance, feel free
to leave any ideas in this thread!


Reply to this email directly or view it on GitHub
https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/issues/32#issuecomment-153538785
.

@stoutj I'm not sure about the timeline for android's removal of mutable containers, but the PR is open here:

https://github.com/ParsePlatform/Parse-SDK-Android/pull/108

I would ask on that PR for an ETA on shipping.

Thanks!
On Nov 3, 2015 8:58 PM, "Richard Ross" [email protected] wrote:

@stoutj https://github.com/stoutj I'm not sure about the timeline for
android's removal of mutable containers, but the PR is open here:

ParsePlatform/Parse-SDK-Android#108
https://github.com/ParsePlatform/Parse-SDK-Android/pull/108

I would ask on that PR for an ETA on shipping.


Reply to this email directly or view it on GitHub
https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/issues/32#issuecomment-153546650
.

Update LDS database schema to allow for query matching in the database layer itself.

This one look like a great candidate to get 90% faster query.

@FreudGit Unfortunately, such a change has HUGE backwards compatibility issues, and likely isn't coming any time soon ;(

@richardjrossiii Even if you convert DB at first usage?

@FreudGit that solution is going to be way too slow for people with huge amounts of objects in their database, plus maintaining migration scripts is a major pain, and something we'd like to avoid.

@richardjrossiii Yes, and no. Conversion can be "in background" (except if\when they are a query, no choice to force on priority thread). You got a point, no real other choice with minimal code impact.
Your DB schema was very simple, should not take long to convert.
You can also try this solution by converting only one field and see the impact.

Just my 2 cents. Continue you excellent work.

@richardjrossiii And, to be honest, converting a large Db can be even faster than the time it take at this time for a query! (3 sec to get 5 row).

@FreudGit There's a lot more to it than that. Any conversion would have to be atomic, as leaving the database in an invalid state would be terrible, and SQLite transactions perform horribly as you have large amounts of tables/rows being created.

Our DB schema is simple, yes, but to flatten out into a different table structure it requires an additional query per-row to fetch the list of columns, and even then, we have no (client-side) strict requirements on the types that can be stored in a given column, which adds even more work to try and figure out, for a given classname, which fields exist for it.

Sure, the schema API now exists server side, but ideally any migration would be able to happen offline.

I really do wish that it was as simple as 'create a new table and migrate everything', but sadly it's not :(

One day perhaps we can tear down the LDS implementation and make something better... One day...

@richardjrossiii Thanks for the update. Let's see the performance gain in 1.10.x, maybe i'll came back after.

Please release. :)

Have a nice day.

Is there a rough timescale for a 1.10 release?

I'm almost ready to go with a client app (native iOS) that is currently wholly reliant on Parse. Central to the app is an LDS query that grabs ONLY around 15 pinned objects from the local store.

These objects are fairly small (~10 columns... all relatively lightweight text fields, plus a couple of pointers and a GeoPoint).

This query was reasonably performant with around 100 pinned objects in the LDS, however I've been 'stress' testing it up to around 2500 objects (this is not beyond the scope of the final production app) and the query is taking waaay too long to complete... sometimes >30s or more which is unacceptable. In comparison, pulling the same objects from the network is as fast as you would expect.

I'd love to know whether 1.10 will fix this issue - otherwise I'm looking at a _major_ refactor of how we manage data locally. :o(

1.10 is going to ship next week, probably close to the end of it.

@nlutsenko Ok - I think I can live with that. Just. Thanks! :o)

@nlutsenko Did you guys forget to ship 1.10 last week? Just wondering... :o)

1.10 is coming this week, hold tight. :grin:

omg im so excited.🙀

Just looked through the release notes - was this fix not included in 1.10.0?

Yup! There was a fix included in 1.10.0 -> #102
Try it, let us know how it goes.

sweet - thanks man, will doo.

can't wait.

I am scared now, since you just raised the expectations bar... :grin:

102 has an impact on performance, quite good impact, but probably not over 9k %%. :)

ok, first feedback:

done some test (on iPhone 6s)

No real speed gain. (But i will test this week-end on a iPhone 5, probably see more the gains.)

This (not-so-funny) thing are that I get the best speed are on the 1.7.2 sdk version
https://docs.google.com/spreadsheets/d/1QSRgTNXMdS4gNC62yA_zfNp4IhdY1SyUYD3xwb_vM1Q/edit?usp=sharing

No real speed gain for us as well. @FreudGit - thanks for the spreadsheet. That was helpful to us.

Ran this query 100 times:
let query = Message.query()!
query.fromLocalDatastore()
query.fromPinWithName("BpPZjxHlfh")
query.whereKey("type", equalTo: "message")
query.limit = 5
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
print("Returned (NSDate().timeIntervalSinceDate(st)))")
}

And averaged about a 1 second.

Sorry - we tested both fromPinWithName and fromLocaDatastore, seperately.

@nlutsenko - that will help for writes, but not reads, correct? Oddly, reads are taking far longer for us from LDS than writes.

Yup. Should help with writes. I'll look into reads next week also.

Thanks @nlutsenko

Seeing the same query performing at 70+ seconds now when 1000 rows in local datastore. Still querying a limit of 20-100 per query.

@nlutsenko Any idea for the next step to get the read faster? :)

@FreudGit Working on it continuously, with #603 as a slight move to that direction.
It's quite a challenging thing to solve, so any help/ideas are very welcome.

At this point, I am trying to parallelize as much work when we do query as possible.
The tricky part is thread-safety here.

@nlutsenko Just an Idea. Make "[query selectKeys:..." work from LDS. And, when the columns are only strings, maybe it can be possible for you to make the query\parsing faster.

@nlutsenko - why not create indices based on query constraints? Return and unwrap only the objects needed.

@kevflynn That's a good one, indeed. Not sure on how feasible this is with our schema though.
Non-sqlite built in indices is a good option here, but would likely require a lot of work.
I found easier to grasp things, like the one surfaced in #605

What's next? :)

@FreudGit Actually, more of the same (as #605), batching stuff together proves to be very helpful in regards to performance, and there is way more we can do in this land. :wink:

I'm still experiencing slow response times on simple queries with the latest SDK. What's the latest?

+1

  • Make LDS query matching more efficient by not loading every object into memory for a specified pin.
  • Update LDS database schema to allow for query matching in the database layer itself.

These two are on the knife edge of deal breakers for me. I do wish I'd been aware of these limitations earlier on.

Is any progress being made on these optimisations? I don't want to sound combative, but calling them optimisations is euphemistic. A database layer that requires loading every object into memory to perform a query, and has no indexing, thus needs to evaluate every object directly ... that's quite something.

Even if the other release blocker issues I'm having are resolved (#768, #772), those two points listed above are leaving me with the realisation that I may have made a terrible mistake.

Particularly #768 is exposing the fatal limitations of the above two points. Failure to unpin is causing a regularly queried pin to grow to thousands of objects over the course of a day, bringing the app to a halt. Simple queries of a table of ~4000 rows should not bring a database to its knees.

Basically I'm looking for some show of confidence that these limitations are actually being worked on, and there is some timeline to seeing them resolved. Otherwise I have to make the painful decision of jumping ship.

I very very much don't want to have to do that after investing several months in a project based on Parse. Especially considering the Parse SDK public APIs (both client and server side) are rather good. They are well designed. They expose smart and powerful functionality through plainly readable and elegant APIs. But the inner limitations and failings I've run up against, both client and server side (though this isn't the place to discuss the latter) have pushed things into the "bad business decision" territory.

Are there any assurances you can give that the above two fixes are going to happen? And in a reasonable timeframe? Are they being actively worked on?

I don't want to jump ship. That would be a timeline disaster for me, and I'm not feeling drawn to any of the competitor's offerings. But I'm reaching the "unpleasant decisions" point.

Aside: I will be working on reproducing #768 in a sample project today, to help that one along, because it is a release blocker and I'm due to beta in a week. Apologies for not having one ready yet, but today is hopefully the day. But even once that one is resolved, the above fundamental performance issues will still remain, leaving the same unpleasant decision to be made.

@sobri909 I switched to using CoreData (through the AlecrimCoreData wrapper) for the local side of my app and it solved a lot of issues for me without taking a lot of time. Basically I just wrote some methods for manually "pinning" parse objects.

@sobri909 I would recommend jumping ship. I gave the team a detailed analysis and harsh reply in May of last year and they haven't done anything about it. They simply don't care about this issue. I'm sorry about your timeline :(

My report: https://developers.facebook.com/bugs/1575754175998187/

@rawrmaan Yes, it's very disappointing.

I'm going to work around it as best I can for beta, but post-beta I'll begin the process of moving away from Parse entirely.

If it were only either issues on the client side or the server side, then sticking with Parse could possibly be justified, while working around specific limitations. But having run into fundamental shortcomings on both client and server, and a seeming lack of interest in remedying them, it's time to make unpleasant decisions.

To the Parse team, the least you could do would be to add a warning to the documentation that the Local Data Store should not be used for more than a few hundred objects. You would save developers from wasting time on doomed implementations.

@nlutsenko are always very open and proactive. Can we please have some feedback about what are the next steps?

2 suggestions:

1- "experimental" query.fromLocalDatastoreAsDirectAccessToDB() (you may just accept Int and String query to start, so more safe)

2- Update LDS database schema to allow for query matching in the database layer itself.

Looking at the current SQLite database structure, I think one quick improvement would be to change pins to be actual SQL relations instead of ... well, what they are now. The current shape of pins in the SQL layer looks like it'd be a considerable bottleneck, and quite possibly a cause of race condition bugs.

That wouldn't solve the two serious points mentioned above, but it does look like low hanging fruit that could have reasonable payoff in the short term, and could be automatically migrated easily enough for existing installs.

It also seems to me that, while in some cases they are useful, named pins are in general an added complexity with more negatives than positives. In an ideal world of a highly performant LDS, I think I would deprecate named pins.

The structure of a well designed database schema coupled with an efficient querying system should be adequate. Any required functionality similar to named pins could / should be added to a schema directly, on a per app basis. And the reduced complexity would be beneficial both to the SDK team(s) and to the users of.

But that's getting way ahead of things. At this stage I think to make any forward progress at all the changes need to be non breaking and relatively atomic. From the discussion above it sounds like the SDK team are essentially blocked on anything that requires larger migrations or backwards compat breaks.

http://blog.parse.com/announcements/moving-on/

Well that explains a lot. Maybe.

Yes, very sad.

@sobri909 @FreudGit Parse is not going away, technically. We have just recently open sourced a compatible back-end for all of our client SDKs (https://github.com/parseplatform/parse-server), and all of these open-source projects will still be maintained well into the future.

There's been some awesome community effort as well to get things like dashboard, as well as one-click heroku hosting.

There's also several killer SDK features that still have yet to be rolled out, so stay tuned! Parse is dead, long live Parse!

@richardjrosiii it's comforting to see how well the transition is being planned, especially the long timeline. Very admirable. But the above issues still remain and there's been no show of commitment from the current SDK team to resolve them.

As it stands, Parse on iOS is unusable for anyone who has local storage requirements beyond a few hundred objects.

The fastest way around that limitation is to use something else for local storage (eg Core Data) and bridge between. It's taken me a couple of days to put that together and I've achieved a speed boost from previously tens of seconds to get results to now getting results effectively instantly.

If I could see a quick path to achieving similar improvements in the Parse SDK itself I'd contribute them, but with the current state of the implementation I just don't think it's there. It's looking like potentially weeks of work. Effectively a ground up rewrite of the LDS.

It's a shame, but the smartest course of action still appears to be to jump ship.

Maybe it would be beneficial to look at an integration with realm.io?

@sobri909 I'm also getting 10s of seconds of load time from local datastore. Would you be able elaborate a little on what you did to do the bridging? Thanks in advance!

@nlutsenko is there still development on improving the LDS? seems like the thread is dead now

@jameslin101 I moved to a combination of CloudKit and Core Data.

Parse's local storage implementation is effectively unusable. So taking Parse without local storage (thus requiring manual bridging to Core Data) and comparing it to CloudKit bridged with Core Data, the pros and cons weigh in CloudKit's favour.

Using Core Data for local storage, I gave each model class a ckRecord property that holds its corresponding CloudKit record.

When the Core Data context is saved I update a saveDate on each object. When a Core Data object is synced to CloudKit I update a ckSaveDate on the object. That makes it easy to find all Core Data objects that require syncing to CloudKit (ie their ckSaveDate is either nil or prior to saveDate).

For syncing changes from CloudKit to Core Data, that's a bit more complex. But generally the starting point (except for initial install/restore) is to use CloudKit subscriptions.

@sobri909 Thank you!

Integrating the to realm.io will be a good idea. I have checkout the database entry

I was thinking also about moving my LDS data to Realm db, which is blazing fast.

Will be really nice to have a useful LDS implementation that allows storing thousands of records.

Would you guys be interesting in implementing a LDS adapter interface so one would be able to register it’s custom adapter and use CoreData, CK, or realm? I’ll gladly help getting you started :)

+1

@flovilmart I'm really interested. My app is only for Android platform right now, so I'll like to implement the adapter, but for android 😊

I was planning to create a simple adapter for my app needs, but I'll prefer to implement it for the Parse SDK 😍

That would be nice, thatMs always better to share the same architectures and patterns on both platforms. What do you think @dplewis?

@flovilmart My app is on iOS and I use core data for a small set of data. I've been meaning to look into realm for a while now. I'm interested in the adapters

@flovilmart what would be the next steps? Will start with iOS only right now?

I'll be happy to help in android 😃

Yeah, perhaps with and architectural proof of concept. Optimizing the storage for queries is where I believe we should go ie: creating additional tables with optimal indexes and then querying the table that holds the data by objectId. What do you think?

This will speed up querying in LDS a lot. Could be a good start.


Anyway, I'm planning to move all my LDS to realm, so I can make queries directly to any data properties. Also, this will speed up a lot saving the data to LDS.

I was thinking about 3 options:

  1. Reuse classes that extend from ParseObject as RealmObject, so data could be serialized directly to realm and users don't have to convert their classes to realm DB. But I don't think that this is possible.
  2. Do it the opposite way, reuse the classes that extend from RealmObject so ParseQuery can serialize to this kind of objects.
  3. Create classes that extend directly from RealmObject, then query ParseObjects and do the conversion. When the conversion is done, save any extra fields existing in the ParseObjectnot defined in the RealmObject, as 3 arrays. An array of string keys, an array of string values and a third array that defines the types to allow deserializing. This arrays will allow saving any new fields.

Then, I was planning to use realm DB directly to make my queries.

To avoid dealing with realm DB, another improvement could be to translate the Parse queries to realm queries.

Those are just some thoughts about this topic.

What do you think?

Is this a good LDS option? Could be a viable solution?

I think the best solution is to have an automatic SQLite / CoreData like you are planning, so users will need only the Parse SDK. But to improve the LDS a lot more, I think that we can write an LDS abstraction that can be saved to some different kinds of DBs like realm or other.

Are there any good ideas / success stories from others going from LDS to Realm? Core Data? Also didn't know LDS had been deprecated.

@flovilmart @dplewis Any news on start a LDS alternative?

Thanks

Not yet

On Sep 5, 2017, 05:49 -0400, Eduard Bosch Bertran notifications@github.com, wrote:

@flovilmart @dplewis Any news on start a LDS alternative?
Thanks

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Hi to all again. I'm starting using the iOS lib and I'll have to implement an LDS.

Has anyone used any alternative to Parse LDS to combine the Parse SDK with Realm or another local database? I think I'll go with Realm as it allows working with lots of entries very efficiently.

I think that this is the biggest issue has Parse right now. With a good LDS, it'll be great.

This issue has been automatically marked as stale because it has not had recent activity. If you believe it should stay open, please let us know! As always, we encourage contributions, check out the Contributing Guide

@eduardbosch what solution did you come to? I wonder whether offering a bridge between Parse and say Realm could be useful. Although we haven't had many recent reports I think that LDS is still not the best especially under high loads.

@Tom-Fox1 I haven't implemented offline data on iOS and I don't know if I'll add it to my app in the near future. I think that LDS is not a good solution for high data DBs with large pins. It'll be really good to have a bridge between Parse and SQLite or Realm. That will make offline data access really shine.

Have you thought about any possible workaround?

@eduardbosch I’ve found that LDS has worked well for me, probably because I only need to pins a very small number of objects. However, I appreciate that it has problems and I see it as one of the most important issues to address with this SDK so I’m just looking into it.

This issue has been automatically marked as stale because it has not had recent activity. If you believe it should stay open, please let us know! As always, we encourage contributions, check out the Contributing Guide

Was this page helpful?
0 / 5 - 0 ratings