no_one

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?

Showing Posts 1 to 10

NobbZ

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

no_one OP

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

OvermindDL1

╰─➤  /usr/bin/time -f "mem=%K RSS=%M elapsed=%E cpu.sys=%S user=%U" -- mix test 
..

Finished in 0.08 seconds
2 tests, 0 failures

Randomized with seed 92121
mem=0 RSS=52428 elapsed=0:01.09 cpu.sys=0.28 user=1.10

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

no_one OP

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

OvermindDL1

Ah, no problem then. And to get a ‘big infinite set of data’ can just read /dev/random or so. ^.^

I took a look and it seems you are using HTTPoison, which uses hackney behind it, and as I recall it does use active: :once on the TCP stack when you pass in active: :once to 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 :observer to 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:

    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

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 that conn can be used as an ‘into’ so the whole streaming part could be replaced with:

    url
    |> HttpStream.stream()
    |> Stream.into(chunked_conn)
    |> Stream.run()

However I think that might accumulate the body, not sure…

I haven’t used chunks in plug yet outside of trivial things… Hmm…

OvermindDL1

OvermindDL1

Oh wait!!!

It’s Stream.map! It’s storing all past sent data!

Just replace Stream.map in your existing code with Stream.each so it doesn’t save the result. (Stream.into might work too? If it doesn’t accumulate, I’m not sure)

no_one

no_one OP

HTTPoison is doing good job, when i replace chunk with other code the memory usage is fine.
I used :observer and 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.each does not improve anything :<

OvermindDL1

OvermindDL1

Well you definitely want each instead of map there as map would have included all past sent data, so… ^.^;

If ‘each’ doesn’t help, maybe try into(chunked_conn)? I’m really surprised each didn’t fix it…

no_one

no_one OP

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

wanton7

You should change your controller function to return conn. Now it returns whatever Stream.run() is returning. So change part of your code to

    conn =
      conn
      |> put_resp_content_type("video/mp4")
      |> put_resp_header("content-length", proxy_headers["content-length"])
      |> send_chunked(200)

    url
    |> HttpStream.stream()
    |> Stream.each(fn n -> chunk(conn, n) end)
    |> Stream.run()

    conn

Not sure it will help with memory problem, but every controller function needs to return Plug.Conn.t()

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews