Speed, Performance, and Public Profile
May 17, 2016
What is a Public Profile?
A public profile page is a guest-accessible LinkedIn member’s profile page. When you visit the same profile as logged-in user, it’s called a member profile. For example, when you visit Jeff’s profile, you see member profile when you are logged in to LinkedIn and you see public profile when you are logged out of LinkedIn. While both these pages show profile information of the same LinkedIn member, they have slightly different content and are optimized for different users.
A member profile is a personalized profile page experience for a LinkedIn member. For example, when I’m looking at Jeff’s profile, the member profile page is personalized for me, showing a “Relationship” module, “How you’re connected” module, etc. Apart from these personalized modules, other modules like “Skills” and “Recommendations” show detailed views of Jeff’s professional identity at LinkedIn.
A public profile on the other hand is optimized for guests (LinkedIn users who have not signed up yet) and search engine crawlers. A public profile page is a preview of the member profile page, without any personalization; each user sees the same public profile. It’s optimized for guests to get enough profile information to get them interested in LinkedIn and to induce them to sign up. It’s also optimized for search engine bots so they can crawl and index public profile pages to help these pages rank higher in search engine results.
This is Jeff Weiner’s public profile page:
Why Speed Matters
Page load time is a critical aspect of user experience. Higher page load time means more users leaving the website without actually seeing the page, resulting in fewer users signing up on website. Page speed is also an important factor for search engine ranking. Google uses page speed in their page rank algorithms. It is a key determinant for Search Engine Optimization (SEO). In addition, a slow page—high page load time—means that there are fewer pages indexed by search engine crawlers on a daily basis. In other words, it takes longer for an update on the public profile to improve a page’s SEO.
Until Q2 2015, the common service served both member and public profile pages with all optimizations targeted for both pages.
Given the different user focus with different optimizations of member and public profile pages, it was clear that we needed different services to host these pages. Public profile pages needed to focus on SEO optimizations, have the ability to alter and add new features, measure their SEO impact, and iterate quickly. The legacy profile service, which hosted both the member and public pages, was based on a legacy internal custom web framework. As a result, we had slower iterations. So, we decided to have a separate front end server to host the public profile page.
Given the importance of page speed for both guest users and search engine crawlers, when we started the rewrite of the public profile, page speed was our highest priority. We started with Play, an open-source web framework along with dust.js, an open-source HTML template framework.
There are a number of smaller modules that comprise a public profile page. The data for each module comes from downstream services via a REST API. For example:
- Main module (showing Summary, Experience, Education, Skills, etc.) data comes from the “identity” service.
- Connection count data comes from the connections service.
- Recommendations modules (showing recommendations received by a member) data comes from recommendations service.
- Groups module (showing the Groups a member belongs to) data comes from groups service.
- Posts module (showing quick-view of Posts written by member on LinkedIn publishing platform) data comes from the publishing service.
- Same-name search module (titled “Find a different Jeff Weiner” showing other members with the same name) data comes from the search service.
- Browsemap module (titled “People also viewed” showing top profile people visited after visiting current one) data comes from the browsemap service.
Apart from these modules, we needed to figure out what the member has chosen to show or hide. So the page also needed access to the “privacy settings” service.
These different service calls also need different data inputs. To gather data, some would need the member’s ID, while others would need the output from other downstream services.
In the Java Promise chain, we made sure that downstream calls were made as soon as possible. They must be resolved quickly so that they do not block public profile rendering.
For example, instead of waiting for the response from the “privacy settings” service to read the member’s settings for groups, posts, and recommendations and then decide to make corresponding service calls, we made requests asynchronously.
We made downstream service calls to groups, posts, and recommendations while making calls to the privacy settings service. Based on the privacy settings response, we decided to use or ignore responses from the downstream services.
For each new module or additional service call, we moved the call to the service as far left as possible in the Java promise chain timeline. With this thoughtful downstream call structure, we reduced server response time by 70 percent.
The default Play configurations that all LinkedIn services start with is good enough for a simple service that handles moderate load. The public profile frontend handles a lot of requests per second. The default Play configurations (specifically the heap memory allocation) caused the service to spend good amount of time in GC. We worked with SREs to optimize GC configuration for the public profile frontend service hosts, so that it optimizes for the behavior and load of the service giving us faster page speeds.
A page that renders on the server has certain advantages. One of them is a fast and near-complete first paint. First paint is when the browser displays initial content. In the case of a public profile, most of the content displays at or near this marker. This is ideal.
Goodbye Page Weight
In public profile, we reduced DOM nodes by 30 percent (1,959 to 1,400 nodes). We also reduced the nesting depth by 25 percent (12 to 9 nodes). Overall, page weight improved 31 percent and coupled with downstream call improvements, page load improved 56 percent.
Deferring loading images improved page load and ensured that the main or primary content rendered first. Combined with image transitioning (fading images in), deferring images reduced page jank (unwanted movement of visual elements as the browser renders the page), which provided a gradual and more aesthetically delightful experience.
Very seldom is performance limited to the front end or back end. It requires teamwork to achieve the most optimal performance.
Public profile frontend work was an amazing team effort and we’d like to thank Shane Afsar, Anand Kishore and Brendan Speth for their constant support during design, implementation and ramping of the project. It would not have been possible to achieve the performance improvements without the immense help from Cliff Snyder. We would also like to thank Ajit Datar who has been constantly a source of encouragement and support, and last but not the least, Ben Lai for all his help reviewing and editing this blog post.