Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live suggestions for homepage package search #1080

Closed
wants to merge 3 commits into from

Conversation

DaniruKun
Copy link

@DaniruKun DaniruKun commented Nov 27, 2021

Rationale

At the moment, users that want to quickly get a reference about a certain package using the web UI, have to perform 2 separate requests:

  1. An initial query in the home page search box
  2. Click on a result in the package index

These additional steps require user's time, and re-doing a query requires a separate request again using a new connection.

Solution

As a PoC, I replaced the homepage search form with a LiveView, with a suggestion box that gets updated with query results from the server.

hexpm-demo.mp4

For now, the Phoenix update trigger is configured so that initial typing start does not send an event (since there isn't much point in performing a search with 1-2 chars), but an event will be sent after 1500ms from the first interaction.
This throttling interval is chosen because the average person's typing speed is 40 WPM, so 60s / 40w = 1.5s / word. Most Elixir packages are composed of a single word, so as soon as the user finishes typing the query, it will be sent to the server and handled. Of course this interval is up for discussion, but in my opinion setting it any lower might introduce too much load on the server.

Potential optimisations and improvements

Caching

I briefly experimented with persisting the package_top assign, and make the server first perform a lookup on that, before falling back to a DB query, but quickly dropped the idea since I don't have enough data to back up that most users search for the top 10 packages (besides, direct links to them are available on the home page already anyway).

One idea could be to keep a small denormalised Mnesia / ETS table in memory for the top N most searched packages, and periodically update it based on search ranking.

Private orgs

Since it is a bit difficult to pass assigns between Conn and Socket, only public org repos will appear in the suggestion results.

Other additions

Closes #1078

@sourcelevel-bot
Copy link

Hello, @DaniruKun! This is your first Pull Request that will be reviewed by SourceLevel, an automatic Code Review service. It will leave comments on this diff with potential issues and style violations found in the code as you push new commits. You can also see all the issues found on this Pull Request on its review page. Please check our documentation for more information.

@DaniruKun DaniruKun changed the title Feature/search suggestions Real time search suggestions for package search Nov 27, 2021
@DaniruKun DaniruKun changed the title Real time search suggestions for package search Live suggestions for homepage package search Nov 27, 2021
@ericmj
Copy link
Member

ericmj commented Dec 1, 2021

There seems to be a mix of EEx and {} syntax used for interpolation. Can we use only <%= %> syntax?

We don't want to make the whole front page a live view because it's resource intensive and a popular page. Can you make only the search component a live view instead?

@DaniruKun
Copy link
Author

DaniruKun commented Dec 1, 2021

There seems to be a mix of EEx and {} syntax used for interpolation. Can we use only <%= %> syntax?

We don't want to make the whole front page a live view because it's resource intensive and a popular page. Can you make only the search component a live view instead?

@ericmj To answer point 1. - technically we can, but it's now considered the "old" syntax that will be deprecated at some point in favour of the HEEx way of doing things, and I think the .heex extension makes it very clear where it can be used.

About 2. - what do you mean?

In my implementation, nothing changes in terms of page load times (or first contentful paint) - the request starts out as a regular HTTP GET and gets upgraded to a WS connection if the client supports it. Of course, you will see a slight increase in memory usage because of the stateful nature of LiveView, however as you'll see we never keep more than 5 package results in the LiveView socket state.
The expensive stats you see on the home page are presented only once, and are then cleared from the state to save memory.
In terms of extra CPU usage, the periodic queries will have a small impact, but also save an extra request and resources that would have otherwise been spent on presenting the package index page with more than 5 results 😊

All things considered, the benefit from the better UX and less page loads outweighs the extra cost of memory and CPU imho.

@ericmj
Copy link
Member

ericmj commented Dec 1, 2021

To answer point 1. - technically we can, but it's now considered the "old" syntax that will be deprecated at some point in favour of the HEEx way of doing things, and I think the .heex extension makes it very clear where it can be used.

Ah yes, we should use the HEEx syntax 👍 .

what do you mean?

Isn't it possible to make section of the page a liveview instead of making the whole page a liveview?

@DaniruKun
Copy link
Author

DaniruKun commented Dec 1, 2021

Isn't it possible to make section of the page a liveview instead of making the whole page a liveview?

Maybe you are talking about Live Components? Even then, they still require a LiveView - you have to replace the default Phoenix Controller with it regardless.

@ericmj
Copy link
Member

ericmj commented Dec 1, 2021

If using a small liveview component in a larger page means that the whole page needs to be changed, like here: https://github.com/hexpm/hexpm/pull/1080/files#diff-ae87547521f78e46f7a0c59f22cc2715f4af4e0b6fc213c3b68b45a08b6456c5R58 and here: https://github.com/hexpm/hexpm/pull/1080/files#diff-ae87547521f78e46f7a0c59f22cc2715f4af4e0b6fc213c3b68b45a08b6456c5R132, along with replacing the controller action with a liveview this does not seem feasible or maintainable.

When looking at the data sent over the websocket after the page load it looks like almost the entire page is sent over the websocket again. I don't understand how render time and CPU usage can be unaffected when it's doing this?

@josevalim
Copy link
Member

@DaniruKun you can do live_render(conn, SomeLiveView) in a regular dead view: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#live_render/3

This way, you can convert only the search input to LiveView+HEEx, leaving the remaining of the page untouched. I believe this is what @ericmj's proposing.

When looking at the data sent over the websocket after the page load it looks like almost the entire page is sent over the websocket again. I don't understand how render time and CPU usage can be unaffected when it's doing this?

To clarify, the concern is not the rendering time per se, but mostly doing the queries twice. It will most likely be fine but given this is the home page, which is likely the most accessed page, starting with only the widget style seems like a sane choice to assess the impact it will have on prod.

@DaniruKun
Copy link
Author

Ah got it, now it's clear. I'll try out that helper then, thanks!

@DaniruKun
Copy link
Author

Implemented it using @josevalim 's suggestion, now the old controller and router should be untouched, and the search LiveView is much simpler. Feel free to test locally with the seeded data and propose adjustments to the event parameters 🙂

@DaniruKun DaniruKun marked this pull request as ready for review December 4, 2021 20:37
Copy link
Member

@ericmj ericmj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good overall but I think the search results should be moved to the dropdown. Now you will get both the browsers suggestions and our search suggestions.

Screenshot 2021-12-08 at 14 56 20

mix.exs Outdated Show resolved Hide resolved
mix.exs Outdated Show resolved Hide resolved

@impl true
def handle_event("search", %{"search" => query}, socket)
when byte_size(query) <= @query_size_limit_bytes do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this check it's not possible to do large search queries at all.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep a guard for this? If so, what should be the limit?

Copy link
Author

@DaniruKun DaniruKun Dec 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good overall but I think the search results should be moved to the dropdown. Now you will get both the browsers suggestions and our search suggestions.

Screenshot 2021-12-08 at 14 56 20

I think the main issue I stumbled into is that the version of Bootstrap in Hex is very old, and does not properly support datalists, so it might need some ugly style hacks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep a guard for this? If so, what should be the limit?

No, a limit is not necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main issue I stumbled into is that the version of Bootstrap in Hex is very old, and does not properly support datalists, so it might need some ugly style hacks.

I think a dropdown may look better. That way we can also add it to the search in the navigation bar.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the datalist - see comment #1080 (comment)

lib/hexpm_web/templates/page/search.html.heex Outdated Show resolved Hide resolved
@DaniruKun
Copy link
Author

I adjusted the template to no longer have a dangling datalist, and instead have my own custom div since as far as I tried, it's kind of impossible to properly make native datalist element options re-render. Plus with the custom approach, you have direct links to the results.

@ericmj
Copy link
Member

ericmj commented Dec 29, 2021

I like this improvement but cannot merge it in its current state because the browser's dropdown overlaps the datalist making it not usable. I think one solution could be to replace the browser's dropdown with our own instead of using a datalist.

@DaniruKun
Copy link
Author

Ah you mean the browser's autocomplete. I think it can be disabled, then there shouldn't be any overlap anymore. The datalist is already gone like I said, so it shouldn't be a problem.

@DaniruKun
Copy link
Author

@ericmj I disabled any form of browser autocomplete/caching for the form, so now it should be clean. Please check that it is the desired behaviour.

@ericmj
Copy link
Member

ericmj commented Jan 30, 2022

I still think that it should be a dropdown because we can add it to the search in the navbar and it's jarring when the content below the search results jumps around when the search results change.

@ericmj
Copy link
Member

ericmj commented Apr 16, 2024

Closing because PR is stale.

@ericmj ericmj closed this Apr 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add search result suggestions in package search bar
4 participants