Packages

phoenix_kit

2.60.2
2.60.3 2.60.2 2.60.1 2.60.0 2.59.0 2.58.0 2.57.1 2.57.0 2.56.1 2.56.0 2.55.1 2.55.0 2.54.2 2.54.1 2.54.0 2.53.0 2.52.2 2.52.1 2.52.0 2.51.0 2.50.0 2.49.1 2.49.0 2.48.0 2.47.0 2.46.0 2.45.0 2.44.0 2.43.1 2.43.0 2.42.1 2.42.0 2.41.6 2.41.4 2.41.3 2.41.2 2.41.1 2.41.0 2.40.1 2.40.0 2.39.0 2.38.1 2.38.0 2.37.5 2.37.4 2.37.3 2.37.2 2.37.1 2.37.0 2.36.1 2.36.0 2.35.0 2.34.0 2.33.0 2.32.1 2.32.0 2.31.1 2.31.0 2.30.0 2.29.1 2.29.0 2.28.2 2.28.1 2.28.0 2.27.2 2.27.1 2.27.0 2.26.1 2.26.0 2.25.0 2.24.0 2.23.3 2.23.2 2.23.1 2.23.0 2.22.24 2.22.23 2.22.22 2.22.21 2.22.20 2.22.19 2.22.18 2.22.17 2.22.16 2.22.15 2.22.14 2.22.13 2.22.12 2.22.11 2.22.10 2.22.9 2.22.8 2.22.7 2.22.6 2.22.5 2.22.4 2.22.3 2.22.2 2.22.1 2.22.0 2.21.5 2.21.4 2.21.3 2.21.2 2.21.1 2.21.0 2.20.0 2.19.0 2.18.1 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.2 2.14.1 2.14.0 2.13.19 2.13.18 2.13.17 2.13.16 2.13.15 2.13.13 2.13.12 2.13.11 2.13.10 2.13.9 2.13.8 2.13.7 2.13.6 2.13.5 2.13.4 2.13.3 2.13.2 2.13.1 2.13.0 2.12.1 2.12.0 2.11.0 2.10.0 2.9.0 2.8.1 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.0 2.0.1 2.0.0 1.7.236 1.7.235 1.7.234 1.7.233 1.7.232 1.7.231 1.7.230 1.7.229 1.7.228 1.7.227 1.7.226 1.7.225 1.7.224 1.7.223 1.7.222 1.7.221 1.7.220 1.7.219 1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules storage key_layout.ex
Raw

lib/modules/storage/key_layout.ex

defmodule PhoenixKit.Modules.Storage.KeyLayout do
  @moduledoc """
  How deep a storage profile fans its keys out (`StorageProfile.key_levels`, V216).

  A file's folder is `<prefix>/<md5>`, with `levels` two-character hash folders
  between them:

      0   lib-1a2b3c/22e8b90781bab72967f9d5410b4299f9/…
      1   lib-1a2b3c/22/22e8b90781bab72967f9d5410b4299f9/…        (the layout so far)
      2   lib-1a2b3c/22/e8/22e8b90781bab72967f9d5410b4299f9/…
      3   lib-1a2b3c/22/e8/b9/22e8b90781bab72967f9d5410b4299f9/…

  Each hash folder has 256 possible names, and MD5 spreads files evenly over
  them, so `n` files leave about `n / 256^levels` file folders in each folder at
  the deepest level. That number is what a local disk feels (listing, backups,
  `rsync`); an object store has no folders and does not care. The layout applies
  to new uploads: a file's folder is stored on its row, so nothing already
  uploaded moves.
  """

  @levels [0, 1, 2, 3]
  @default 1
  @fanout 256

  # Folders per directory up to which listing and backing up stay quick, and up
  # to which a disk still copes. Judgement calls, in one place.
  @comfortable 5_000
  @tolerable 50_000

  @doc "The layouts a profile may choose: the number of hash folders."
  @spec levels() :: [0..3]
  def levels, do: @levels

  @doc "The layout every profile had before it could be chosen (V216)."
  @spec default() :: 1
  def default, do: @default

  @doc "Whether `levels` is a layout."
  @spec valid?(term()) :: boolean()
  def valid?(levels), do: levels in @levels

  @doc """
  The folder of a file: `prefix`, `levels` hash folders, then the MD5.

      iex> PhoenixKit.Modules.Storage.KeyLayout.path("lib-1a2b", "22e8b907", 0)
      "lib-1a2b/22e8b907"
      iex> PhoenixKit.Modules.Storage.KeyLayout.path("lib-1a2b", "22e8b907", 1)
      "lib-1a2b/22/22e8b907"
      iex> PhoenixKit.Modules.Storage.KeyLayout.path("lib-1a2b", "22e8b907", 2)
      "lib-1a2b/22/e8/22e8b907"
  """
  @spec path(String.t(), String.t(), non_neg_integer()) :: String.t()
  def path(prefix, md5, levels) when levels in @levels do
    folders = for i <- 0..(levels - 1)//1, do: String.slice(md5, i * 2, 2)
    Enum.join([prefix | folders] ++ [md5], "/")
  end

  def path(prefix, md5, _levels), do: path(prefix, md5, @default)

  @doc """
  About how many file folders the busiest folder holds once a library has `files`
  files, under `levels`.

      iex> PhoenixKit.Modules.Storage.KeyLayout.per_folder(1_000_000, 1)
      3907
      iex> PhoenixKit.Modules.Storage.KeyLayout.per_folder(1_000_000, 0)
      1000000
  """
  @spec per_folder(non_neg_integer(), 0..3) :: non_neg_integer()
  def per_folder(files, levels), do: ceil(files / Integer.pow(@fanout, levels))

  @doc "How a folder of `per_folder` entries fares: `:comfortable`, `:slow` or `:too_many`."
  @spec rating(non_neg_integer()) :: :comfortable | :slow | :too_many
  def rating(per_folder) when per_folder <= @comfortable, do: :comfortable
  def rating(per_folder) when per_folder <= @tolerable, do: :slow
  def rating(_per_folder), do: :too_many

  @doc "The library sizes the Storage profiles tab compares."
  @spec sizes() :: [pos_integer()]
  def sizes, do: [10_000, 100_000, 1_000_000, 10_000_000, 100_000_000]

  @doc """
  One row per library size: `{files, per_folder, rating}` under `levels`.
  """
  @spec table(0..3) :: [{pos_integer(), non_neg_integer(), atom()}]
  def table(levels) do
    for files <- sizes() do
      per = per_folder(files, levels)
      {files, per, rating(per)}
    end
  end

  @doc "The number of files at which `levels` stops being comfortable."
  @spec comfortable_up_to(0..3) :: pos_integer()
  def comfortable_up_to(levels), do: @comfortable * Integer.pow(@fanout, levels)
end