Powered by AppSignal & Oban Pro

Buffered and ranged streams with AzureSDK

livebooks/streaming.livemd

Buffered and ranged streams with AzureSDK

Mix.install([
  # {:azure_sdk, path: Path.expand("..", __DIR__)},
  {:azure_sdk, "~> 0.4.1"}
])

Overview

upload_stream/5 stages Put Block chunks (default 4 MiB) and commits with Put Block List. download_stream/4 fetches the blob with HTTP Range requests.

Requires Azurite (docker compose up -d). If Blob calls return HTTP 500, the Azurite data volume is usually corrupted: recreate it with docker compose down -v && docker compose up -d. If Docker itself reports overlay I/O errors, restart Docker Desktop, then recreate the volume. As a workaround you can run npx azurite --location /tmp/azurite-ex-azure --blobHost 127.0.0.1 on ports 10000–10002.

credential =
  AzureSDK.Identity.SharedKeyCredential.new(
    "devstoreaccount1",
    "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
  )

client =
  AzureSDK.Storage.Client.new(
    account: "devstoreaccount1",
    credential: credential,
    endpoint: "http://127.0.0.1:10000/devstoreaccount1"
  )

container = "stream-demo"

case AzureSDK.Storage.Container.exists?(client, container) do
  true -> :ok
  false -> {:ok, _} = AzureSDK.Storage.Container.create(client, container)
  {:error, error} -> raise "Azurite Blob is not reachable: #{error.message}"
end

Stream upload

stream = Stream.repeatedly(fn -> "chunk-data-" end) |> Stream.take(20)

case AzureSDK.Storage.Blob.upload_stream(client, container, "chunks.bin", stream,
       block_size: 32
     ) do
  {:ok, _} ->
    :ok

  {:error, %{status: 500} = error} ->
    raise """
    Azurite returned HTTP 500 (#{error.message}). Recreate its data volume \
    (docker compose down -v && docker compose up -d) or run a temporary \
    npx azurite on ports 10000-10002.
    """

  {:error, error} ->
    raise "upload_stream failed: #{inspect(error)}"
end

Stream download

{:ok, stream} =
  AzureSDK.Storage.Blob.download_stream(client, container, "chunks.bin", chunk_size: 64)

stream |> Enum.join() |> byte_size()

Clean up

{:ok, :deleted} = AzureSDK.Storage.Blob.delete(client, container, "chunks.bin")
{:ok, :deleted} = AzureSDK.Storage.Container.delete(client, container)
:ok