Pitfalls of 'together': A case study

Preface: The goal of the following is to create a discussion on – or at minimum get people thinking about – the potential downsides of something the Ember community typically praises.

I’ve been using Ember since pre-1.0 before ember-cli was ember-cli. It’s served me extremely well over the years. I have run two successful consultancies with Ember being one of our core differentiators, and worked one full-time position also primarily for my Ember skills. I am deeply grateful to the people who have committed thousands (hundreds of thousands?) of hours creating, maintaining, and improving Ember and its ecosystem.

I’ve benefited from what Yehuda recently described as the ‘togetherness’ of Ember and its community. I agree there is a lot of value in ‘togetherness’, and it is that togetherness that has brought Ember (and me) where we are today.

Recently we’ve been working on the problem of rendering very large tables (50k cells) quickly for printing from the browser. In the process, we have come across a few things I would like to point out as downsides of togetherness.

Requirements of the table:

  • Render 50k cells in a ‘reasonable’ amount of time

  • All data must be visible at the same time, no pagination, occlusion / smoke-and-mirrors. If it’s not visible, it won’t print.

  • Because it’s for print, it does not need interactivity.

We first researched within the Ember community Discord/Discourse archives for similar questions. There are a few questions out there on this, and they all have essentially the same response: Don’t render large tables. While I personally agree with this solution, it does not change the business requirements I’m faced with.

Then we tried a few approaches:

  • Use pure function helpers for prepping cell data for display

  • Use a custom component manager to manage custom cell components

  • Cache translations and values aggressively

  • Cache internationalized values aggressively

  • Manually concat document fragments, then only when finished, attach to the DOM

Of these, only the last one (keep everything out of the DOM) provided any significant improvement in render time. Creating our own virtual DOM offscreen was sufficiently fast, but not without a major downside:

  • We can now only render raw strings to each table cell. Specifically we can no longer render Ember components in a table cell.

As far as we understand, glimmer does something similar where it composes document fragments off-screen and only attaches them to the DOM when absolutely necessary. We were specifically looking at htmlbars and glimmer itself.

After we felt we learned everything we could from what was already asked/answered and reading source code, we started posting to Discord. This is really where community togetherness started to show its weaknesses for us in this case. Even with clearly stated questions and explanations of the problem, answers roughly fell into two buckets:

  • Don’t render large tables. This response was annoying more than anything. It was clear the responder had not taken the time to even read the requirements, but instead drew from the ‘together’ answer.

  • Vague. Supposedly there are other tools (in-element, ember-table, vertical-collection) that do what we are trying to do, but after reading through their sources, none seem to.

We found reference in a few places to createTemplateFactory, precompileTemplate, compileTemplate methods, but when we asked about them, the answer was:

  • Render the table in each loops in the handlebars template.

Yeah we get it. Ember is a together framework and that has benefits. We have benefited. But does togetherness mean when someone needs to do something non-standard APIs are either not made public or made public and not documented? Further, does it mean that every response that is given by the community needs to be the ‘together’ answer, even when that answer clearly doesn’t solve the problem?

In order to meet our requirements, we have had to port Ember components over to helpers which we are able to call as raw functions from our table’s Typescript file. It’s not terrible. But it’s frustrating that something so fundamental in other frameworks (ReactDOM.render()) and seems like it should be – or is – possible in Ember but instead are not only difficult (impossible?) in Ember, but also that the community is so into being together it provides a few canned responses even when the responses don’t fit the situation.

For reference, here is a table using Handlebars each loops with 50k cells (>25s load time):

And here is one using document fragments (>13s load time):

While the document fragment approach isn’t blazing fast, it is twice as fast as Ember/Handlebars.

Thank you for reading. Please reply if you have any thoughts on how we can/should approach community togetherness differently when trying to solve problems that fall off the beaten path.

3 Likes

Firstly, I want to say that your situation sounds really frustrating, and I’m sorry :frowning:

I think it’s natural for engineers, when faced with a very difficult and costly task, to try to hack around it, to ask if it really needs to be rendered all at once, if those really are the requirements. But sometimes, they are, and they aren’t changing, so the ever repetitive “why’re you doing that? Just do something different!” in chat can be exceptionally annoying. I always try to start by asking what the requirements are, and why they are what they are, before I suggest an alternative strategy, but this tends to happen to all of us I think.

But back to the problem at hand. You have a unique situation - most users wouldn’t want a 13s render time anywhere in their application, but it makes sense for you, and you want to give your users the best solution you can given the constraint. Totally get it.

I can also see how it can seem like Ember’s togetherness is working against you. But I think this is a bit of misunderstanding. At least, the way I understand togetherness definitely doesn’t mean that we all try to fit inside the same box, so to speak.

It’s moreso that we want to make sure the community can move forward, together. That we find the best solutions to particular problems, together. Not that we try to solve 80% of the problems, and then duck our heads in the sand and pretend the other 20% don’t exist, or shrug and say they’re just too hard. Because they definitely aren’t.

Moving forward together means we need to think things through, though. We need to make sure that we don’t release a half-baked API that a lot of folks, like you, try to use to solve unique problems, only to have it break later on when we need to change something more fundamental in the system. For example, the low level way to render components in Ember has completely changed between Ember v1, v2, and v3 many times in ways both small and large. There were a few APIs like this in the v1 cycle, like ListView for instance, that really hurt users of them and made it almost impossible to upgrade.

Personally, I think that making the low level APIs public is absolutely a goal. I’m actually currently working toward it with Glimmer.js v2 at the moment, which will be the next stepping stone toward eventually unifying Glimmer.js and Ember.js, and delivering on “install your way to Ember”. And it will include a renderComponent() function, at it’s core :smile:

Unfortunately it takes time. We’re working on it, but I don’t think we can say it, or any other low level feature necessarily has a timeline yet, in large part because it’s hard to know when you’ve hit on the right public API for a low-level construct :confused: for instance, 6 months ago I was certain we were ready to release the low-level autotracking APIs. I am now very glad we didn’t, because we iterated a couple more times and the end result is going to be much much better.

When you get these abstractions wrong, it often leads to fragmentation. Both due to breakage over time, and due to caveats with a given approach, and a mismatch between low and high-level APIs. When you get them right, everything builds on each other. We’ve seen this in our success with component and modifier managers, and I think we’ll see it more as we continue to rationalize the internals, and open up more APIs over time.

This is what togetherness means to me. We make sure that what we release is solid, and we climb the mountain together. I think it’s important that as a community, we remember this. It doesn’t mean we force everyone into the same box - it means we find the best solution to a given problem, and over time we improve those solutions together. This is especially true in Discord, where the herd can seem to dominate - we need to make sure we listen to people when the come in and ask questions, and not assume that they’re just doing something wrong.

4 Likes

Thank you for your reply, Chris. I would also like to thank you for your work on Ember. Modifiers, auto-tracking, destroyables are some of my favorite recent changes :sparkles: : they all provide more flexibility and low-level functionality which makes Ember apps easier to write and maintain. Thank you too for your blog posts. Recently, they’ve been a most useful source for learning.


most users wouldn’t want a 13s render time anywhere

I should have been more clear about our constraints: We chose 50k cells so we could easily measure time differences, our actual requirement is ~23k cells, which takes the render time down to ~4.5s. Still super slow :snail: , but we are accepting it for now.

Server-generated reports for print are on our roadmap. However, they could not make it into this release due to time constraints.

togetherness definitely doesn’t mean that we all try to fit inside the same box… make sure the community can move forward, together. That we find the best solutions to particular problems, together.

This makes sense. However, due to prioritization and things taking time as you mentioned, the end result at any given time is ‘the box Ember provides and everyone must fit into.’ It’s not the intention or goal, but the result.

we need to think things through, though. We need to make sure that we don’t release a half-baked API

Understood and appreciated :pray:

making the low level APIs public is absolutely a goal

I think this is key, and is something that wasn’t clear to me before your response. It’s inevitable that a framework will always provide ‘a box’ for consumers, but if the low-level APIs are available, the box can be more easily adapted to different situations.

Unfortunately it takes time. We’re working on it

Understood and appreciated.

I don’t think we can say it, or any other low level feature necessarily has a timeline yet

Naive, unsolicited comment/opinion from someone not doing the actual work: I think strict prioritization on what is developed for the framework would help this: foundational building blocks / low-level APIs come first, everything else is future.

Again naive, possibly unrealistic, example: Instead of improving things like application structure with template co-location, pods, etc. (the current structure works), untangle query params from routes and controllers (one of the more painful/limiting building blocks of the framework IMO). The broad heuristic would be: If it works and is not a foundational building block / low-level API, it gets bumped to future.

what we release is solid, and we climb the mountain together

I appreciate that. Some of the motivation for posting in the first place is because I love working with Ember :gift_heart: and the large table feature is forcing me to port things from Ember components back out to pure JS functions. It feels more like I’m climbing two separate mountains :mountain: :mountain: at the same time, or down the Ember mountain to go up a different one.


Two things seem more clear to me from your response:

  • Exposing low-level APIs is key. I now realize this is why I have appreciated the outcome of your work recently: most are targeting low-level things.
  • Prioritizing only low-level APIs would/could help Ember be more flexible sooner.

Thank you for your thoughts and time.

Just wanted to add a few possibilities that can also give significant speed boosts in these situations. Maybe you already tried these, but in case other people find this thread it can be helpful to know:

  1. Are all the components you’re trying to render in the big table purely glimmer components (no classic components)? If you didn’t try that, try it. There is a real performance difference. It’s even bigger if you can use only template-only glimmer components.

  2. Are you using <LinkTo> in the table? It can be slow unfortunately, and you can sometimes get a massive speedup by using something like href-to.

@patrickberkeley I have worked on a few apps which required the ability to print large documents and so I feel your pain. In addition the pages that need to print were frequently evolving and required variable height UI widgets with specific break point requirements. In the past I had reached for server side solutions for this, but my experiences there were never great.

So, for a project two years ago, I took a different approach. I created an addon (GitHub - forge512/ember-printable-pages: Create beautiful, printable documents using Ember components) which enables you to define a printable document in Ember using your existing components. You declare what you want in the document and it does the hard work of laying out your content into pages (doing smart things with page breaks). These pages render progressively so the user observes progress even for large documents (100’s of pages). Even better, the pages are easy to write, test, adapt, and maintain.

I’ve been using it in production for ~2 years now and it has been amazing. I’ve been able to add/remove/adapt content in these docs with a fraction of the effort it took in the server rendered solutions. The only reason I haven’t publicized the addon is because there are no docs :(. I’ll endeavor to get some basic docs up over the next week. I’ll probably also give a talk about it at the Ember ATX meetup some time in the next few months (even if it is a virtual meetup).

Demo

Here is a little demo of it working in a real app. Note the page you are seeing is just HTML, but with page breaks and css magic so it prints well. Also note as I change the settings in the top menu the document automatically rerenders. You will notice evidence of the progressive rendering by the page numbers incrementing in the top right corner of the page.

2 Likes

Again naive, possibly unrealistic, example: Instead of improving things like application structure with template co-location, pods, etc. (the current structure works), untangle query params from routes and controllers (one of the more painful/limiting building blocks of the framework IMO). The broad heuristic would be: If it works and is not a foundational building block / low-level API, it gets bumped to future.

We’ve been operating on this level more and more I’d say, at least as long as I’ve been on the core team. The issue in part is detangling a monolith in general is hard work, and a lot of things are actually quite foundational when you might not expect them to be. But I agree, we need to focus on cutting back complexity and refining our internals now that Octane is out. I think it’s a good time to do that :smile:

It feels more like I’m climbing two separate mountains :mountain: :mountain: at the same time, or down the Ember mountain to go up a different one.

I know this feeling all too well :disappointed: hopefully we can address your issues sooner rather than later, and in general begin exposing more primitives to make it easier. I hear you, the core team hears you, and we’re working on it!

Thank you for the ideas, Edward.

  1. Are all the components you’re trying to render in the big table purely glimmer components

Yes they are all glimmer

  1. Are you using <LinkTo> in the table

No we swapped out link-to with href-to for performance awhile ago.

1 Like

Thank you for the info and sharing your ember-printable-pages, Chris. I’ll definitely be checking it out and will follow up with question or successes.

Thanks again, Chris.

We’ve been operating on this level more and more I’d say…

:+1:

hopefully we can address your issues sooner rather than later, and in general begin exposing more primitives to make it easier. I hear you, the core team hears you, and we’re working on it!

:heart_eyes: Okay, thank you for listening.

I have deployed some docs for ember-printable-pages. They are not quite complete, but they are enough to get started. I’ll fill in more of the ‘cookbook’ pages over the next few days.

https://forge512.github.io/ember-printable-pages/