no_one
Hello, I have discovered Elixir recently and I have decided to do some projects with it (and Phoenix).
I need to take file from one server and stream it to client through my server. I wanted to do it with streams. My HttpStream looks almost exactly as this: https://labs.civilcode.io/elixir/2018/05/03/stream-files.html.
Streaming works but when I want to access bigger file (for example ~4 GB movie) my application consumes large amounts of memory, and after ~20 seconds video stops playing.
My controller’s function looks like this:
def show(conn, _params) do
url = "http://localhost:8080/Bigfile.mp4"
%{headers: proxy_headers} = HTTPoison.head!(url)
proxy_headers = for {k, v} <- proxy_headers, do: {String.downcase(k), v}, into: %{}
chunked_conn =
conn
|> put_resp_content_type("video/mp4")
|> put_resp_header("content-length", proxy_headers["content-length"])
|> send_chunked(200)
url
|> HttpStream.stream()
|> Stream.map(fn n -> chunk(chunked_conn, n) end)
|> Stream.run()
end
It’s not beautiful but I’ll improve that later. From my observations chunk/2 is called too fast (if I insert :timer.sleep(1) before stream_next(resp) memory usage is normal but it’s much slower).
This is memory usage from :observer (images are from imgur and they appear to be cropped here):
https://imgur.com/SSOjNie
And very interesting thing is that one process has very long message queue:
https://imgur.com/leANyk4
How to fix this?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
NobbZ
Only from reading roughly your post, not the linked code… But it seems as if data comes faster as you are able/willing to process it.
Perhaps you need some rate limiting on the reading end? But probably you need to change a lot of code to do so.
no_one
My bad, here is the code: GitHub - Arquanite/phoenix-streaming-app: This example code shows big memory requirements of the application :( · GitHub
I hope there is a way to get more data from original server only after data is sent to client (normal http servers must be working similar way?).
OvermindDL1
I’m running your tests but I’m not seeing high memory usage here. What’s the specific test to run that shows the problem?
I’m still thinking the issue is just what @NobbZ said, that the data is being received and stored without being throttled faster than it can resend it out.
no_one
There’s no tests for this (I don’t have much experience in testing and don’t know how to test this without uploading really big binary somewhere).
I’m testing this this way:
http-server (from npm) is listening on port 8080 (and serving my video file named “Bigfile.mp4”). Then I open my browser on
localhost:4000/api/files/1(id doesn’t matter) and it starts loading video.As you can see there is really small amount of code so I can make something new that will be more efficient. But I don’t know how.
OvermindDL1
Ah, no problem then. And to get a ‘big infinite set of data’ can just read
/dev/randomor so. ^.^I took a look and it seems you are using
HTTPoison, which useshackneybehind it, and as I recall it does use active: :once on the TCP stack when you pass inactive: :onceto HTTPoison, so that should be fine… Maybe it’s some growing memory somewhere rather than something actually being stored…At this point I’d really use
:observerto see which process is allocating that memory then run a GC on that process, if that lowers the memory then it’s just unused memory that hasn’t used enough of the system memory to cause a GC within it’s time yet (and there are a few fixes for this, but eh). If it’s actually allocated memory though then something is holding on to it, which could be hackney, httpoison, or Stream from what I see in your code, and I doubt it would be Stream. Let’s look at the consumer perhaps…Hmm, in your controller:
Hmm, as I recall the response body get’s accumulated in the
conn.resp_body, but that’s not being accumulated here. I do know thatconncan be used as an ‘into’ so the whole streaming part could be replaced with:However I think that might accumulate the body, not sure…
I haven’t used chunks in plug yet outside of trivial things… Hmm…
OvermindDL1
Oh wait!!!
It’s
Stream.map! It’s storing all past sent data!Just replace
Stream.mapin your existing code withStream.eachso it doesn’t save the result. (Stream.into might work too? If it doesn’t accumulate, I’m not sure)no_one
HTTPoison is doing good job, when i replace
chunkwith other code the memory usage is fine.I used
:observerand it shows which process is consuming memory (screen is in first post). It’s something related to cowboy. I’m really a beginner but I think the problem is with this long message queue (and gc won’t help with it?).And
Stream.eachdoes not improve anything :<OvermindDL1
Well you definitely want
eachinstead ofmapthere asmapwould have included all past sent data, so… ^.^;If ‘each’ doesn’t help, maybe try
into(chunked_conn)? I’m really surprisedeachdidn’t fix it…no_one
Still nothing, it loads whole file into memory (it uses 4.6 GB of RAM, almost exact size of my video)
EDIT: Maybe I should use something else than conn, but what and how integrate it into Phoenix app?
wanton7
You should change your controller function to return
conn. Now it returns whateverStream.run()is returning. So change part of your code toNot sure it will help with memory problem, but every controller function needs to return
Plug.Conn.t()