# 9. ETS (Erlang Term Storage)

### 📋 ÍNDICE

1. Visão Geral do ETS
2. Tipos de Tabelas ETS
3. Criando e Configurando Tabelas
4. Operações Básicas: CRUD
5. Consultas e Pattern Matching
6. Iteração e Busca Avançada
7. ETS vs DETS vs Mnesia
8. Performance e Otimização
9. Concorrência e Acesso Multi-processo
10. Match Specifications
11. Boas Práticas e Padrões Comuns
12. Resumo Técnico
13. Referência Rápida

***

### 1. VISÃO GERAL DO ETS

O **ETS (Erlang Term Storage)** é um banco de dados **em memória** embutido na BEAM, projetado para armazenar grandes volumes de dados com acesso extremamente rápido. Diferente do Mnesia (que é transacional e distribuído), o ETS é focado em **performance máxima** para uso dentro de um único nó BEAM.

#### Características Fundamentais do ETS

O ETS é uma das razões pelas quais sistemas Erlang/Elixir conseguem manter performance excepcional mesmo com grandes quantidades de dados em memória. Tabelas ETS são **compartilhadas entre processos** e otimizadas para leitura e escrita concorrente.

```yaml
CARACTERÍSTICAS PRINCIPAIS DO ETS:

  🎯 PROPÓSITO:
    - Banco de dados em memória de alta performance
    - Armazenamento de termos Erlang/Elixir (qualquer dado!)
    - Compartilhado entre processos na mesma VM
    - Sem transações ACID (dados sujos são possíveis)
  
  🔑 DIFERENCIAIS ÚNICOS:
    - Velocidade: operações em microssegundos
    - Compartilhado: tabelas acessíveis por múltiplos processos
    - Flexível: qualquer termo Erlang pode ser armazenado
    - Eficiente: estruturas otimizadas (árvores, hash tables)
    - Sem garbage collection: dados fora da heap dos processos
  
  📊 MÉTRICAS DE PERFORMANCE:
    - Leitura por chave: O(1) para hash tables, O(log n) para árvores
    - Escrita: até 1-5 milhões de ops/segundo
    - Memória: overhead mínimo por registro
    - Concorrência: leitura concorrente sem locks
  
  🔄 TIPOS DE TABELA:
    - :set - chaves únicas, sem ordenação (hash table)
    - :ordered_set - chaves únicas, ordenadas (árvore)
    - :bag - múltiplos registros com mesma chave
    - :duplicate_bag - bag otimizado para duplicatas
```

#### ETS vs Mnesia: Quando Usar Cada Um?

É comum haver confusão entre ETS e Mnesia. A escolha certa depende das necessidades do seu sistema.

```yaml
MNESIA (BANCO DE DADOS COMPLETO):
  ✅ Transações ACID
  ✅ Distribuição entre nós
  ✅ Persistência em disco
  ✅ Recuperação após falhas
  ❌ Mais lento (overhead de transações)
  ❌ Configuração mais complexa

ETS (CACHE/RÁPIDO):
  ✅ Extremamente rápido (microssegundos)
  ✅ Simples de usar
  ✅ Compartilhado entre processos
  ✅ Sem overhead de transações
  ❌ Não persistente (dados são voláteis)
  ❌ Sem distribuição nativa
  ❌ Sem rollbacks ou isolamento
```

```elixir
# Caso típico de uso do ETS: cache compartilhado
defmodule UserCache do
  @table :user_cache
  
  def start do
    # Cria tabela ETS pública
    @table = :ets.new(:user_cache, [:set, :public, :named_table])
  end
  
  def get_user(id) do
    case :ets.lookup(@table, id) do
      [{^id, user}] -> {:ok, user}
      [] -> 
        # Cache miss - busca no banco real
        case DB.get_user(id) do
          {:ok, user} ->
            # Armazena no cache
            :ets.insert(@table, {id, user})
            {:ok, user}
          error -> error
        end
    end
  end
  
  def invalidate(id) do
    :ets.delete(@table, id)
  end
end
```

***

### 2. TIPOS DE TABELAS ETS

Cada tipo de tabela ETS tem características específicas de performance, uso de memória e ordenação.

#### 2.1 Tabela :set (Hash Table)

O tipo mais comum. Oferece acesso O(1) por chave, mas os registros não são ordenados.

```elixir
# Tabela :set - acesso rápido por chave única
table = :ets.new(:minha_tabela, [:set])

# Inserção
:ets.insert(table, {:chave, "valor"})

# Busca O(1)
:ets.lookup(table, :chave)  # [{:chave, "valor"}]

# Tabela :set não mantém ordem
:ets.insert(table, {:a, 1})
:ets.insert(table, {:b, 2})
:ets.insert(table, {:c, 3})

# A ordem de retorno é arbitrária
:ets.tab2list(table)  # Pode ser qualquer ordem
```

#### 2.2 Tabela :ordered\_set (Árvore)

Mantém registros ordenados pela chave, permitindo range queries eficientes, mas com acesso O(log n).

```elixir
# Tabela :ordered_set - dados ordenados
table = :ets.new(:tabela_ordenada, [:ordered_set])

# Inserção mantém ordem
:ets.insert(table, {:c, 3})
:ets.insert(table, {:a, 1})
:ets.insert(table, {:b, 2})

# Retorna ordenado por chave
:ets.tab2list(table)  # [{:a,1}, {:b,2}, {:c,3}]

# Range queries com next/prev
:ets.next(table, :a)  # :b (próxima chave)
:ets.prev(table, :c)  # :b (chave anterior)

# Busca todos entre :a e :c (exclusivo)
def range_query(table, from, to) do
  collect_until(table, from, to, [])
end

defp collect_until(table, current, to, acc) do
  case :ets.next(table, current) do
    ^to -> acc
    key -> 
      value = :ets.lookup(table, key)
      collect_until(table, key, to, [value | acc])
  end
end
```

#### 2.3 Tabela :bag (Múltiplos Registros)

Permite múltiplos registros com a mesma chave, sem ordem específica.

```elixir
# Tabela :bag - chaves duplicadas (sem ordem)
table = :ets.new(:tabela_bag, [:bag])

# Pode inserir múltiplos registros com mesma chave
:ets.insert(table, {:user, "joao", "login"})
:ets.insert(table, {:user, "joao", "logout"})
:ets.insert(table, {:user, "joao", "compra"})

# Retorna TODOS os registros com a chave
result = :ets.lookup(table, :user)
# [{:user, "joao", "login"}, 
#  {:user, "joao", "logout"}, 
#  {:user, "joao", "compra"}]

# Deletar todos com a chave
:ets.delete(table, :user)  # Remove todos os registros com chave :user

# Deletar registro específico (compara tudo)
:ets.delete_object(table, {:user, "joao", "login"})
```

#### 2.4 Tabela :duplicate\_bag (Bag Otimizado)

Similar ao `:bag`, mas otimizado para inserção de múltiplas duplicatas idênticas.

```elixir
# :duplicate_bag - otimizado para duplicatas idênticas
table = :ets.new(:tabela_duplicate_bag, [:duplicate_bag])

# Inserir mesmo registro múltiplas vezes é eficiente
:ets.insert(table, {:evento, "click", 100})
:ets.insert(table, {:evento, "click", 100})  # Contagem interna

# lookup retorna com contagem implícita
:ets.lookup(table, :evento)  # [{{:evento, "click", 100}}] (aparentemente 1)

# Na prática, :duplicate_bag é menos usado
# Prefira :bag para bags normais
```

#### 2.5 Tabelas Nomeadas vs Anônimas

```elixir
# Tabela anônima (referência retornada)
table_ref = :ets.new(:tabela_anonima, [])
# table_ref é um número inteiro/não utilizável diretamente

# Tabela nomeada (acessível globalmente por átomo)
:ets.new(:tabela_nomeada, [:named_table])

# Agora pode acessar pelo nome diretamente
:ets.insert(:tabela_nomeada, {:chave, "valor"})
:ets.lookup(:tabela_nomeada, :chave)

# Verificar se tabela existe
:tabela_nomeada in :ets.all()  # true

# Cuidado: tabelas nomeadas podem causar conflitos de nomes
```

***

### 3. CRIANDO E CONFIGURANDO TABELAS

#### 3.1 Opções de Criação de Tabelas

```elixir
defmodule TableCreation do
  @moduledoc """
  Demonstra todas as opções de criação de tabelas ETS.
  """
  
  # Tabela básica
  def basic_table do
    :ets.new(:tabela_basica, [
      :set,                    # Tipo de tabela
      :public,                 # Acesso público (qualquer processo)
      :named_table            # Nome global
    ])
  end
  
  # Tabela com acesso privado
  def private_table do
    :ets.new(:tabela_privada, [
      :set,
      :private,               # Só o processo criador acessa
      :named_table
    ])
  end
  
  # Tabela protegida (leitura pública, escrita privada)
  def protected_table do
    :ets.new(:tabela_protegida, [
      :set,
      :protected,             # Leitura pública, escrita privada
      :named_table
    ])
  end
  
  # Tabela com chave no meio do registro
  def table_with_custom_keypos do
    # Registro: {:usuario, id, nome, email}
    # Queremos que a chave seja o segundo elemento (posição 2)
    :ets.new(:tabela_chave_personalizada, [
      :set,
      :named_table,
      {:keypos, 2}  # Chave está na posição 2 da tupla
    ])
    
    # Agora o lookup usa a posição 2 como chave
    :ets.insert(:tabela_chave_personalizada, {:usuario, 42, "João", "joao@x.com"})
    :ets.lookup(:tabela_chave_personalizada, 42)  # Busca pelo 42 na posição 2
  end
  
  # Tabela com escrita concorrente otimizada
  def concurrent_write_table do
    :ets.new(:tabela_concorrente, [
      :set,
      :public,
      :named_table,
      {:write_concurrency, true}   # Otimizada para múltiplos escritores
    ])
  end
  
  # Tabela com leitura concorrente otimizada
  def concurrent_read_table do
    :ets.new(:tabela_leitura_concorrente, [
      :ordered_set,                     # Necesário para read_concurrency
      :public,
      :named_table,
      {:read_concurrency, true}        # Otimizada para muitos leitores
    ])
  end
  
  # Tabela com compressão (economia de memória)
  def compressed_table do
    :ets.new(:tabela_comprimida, [
      :set,
      :public,
      :named_table,
      {:compressed, true}              # Comprime dados na memória
      # ⚠️ Compressão tem custo de CPU para leitura/escrita
    ])
  end
  
  # Tabela com tamanho inicial pre-alocado
  def pre_allocated_table do
    :ets.new(:tabela_pre_alocada, [
      :set,
      :public,
      :named_table,
      {:heir, self(), []},            # Herdeiro em caso de morte do criador
      {:size, 100000}                 # Tamanho inicial (ajuda performance)
    ])
  end
end
```

#### 3.2 Opções de Acesso

```yaml
OPÇÕES DE ACESSO (ACCESS PERMISSIONS):

  :private:
    - Apenas o processo criador pode ler/escrever
    - Útil para dados internos de um GenServer
    - Max performance (sem locks)
  
  :protected:
    - Todos os processos podem LER
    - Apenas criador pode ESCREVER
    - Útil para caches de leitura intensiva
  
  :public:
    - Qualquer processo pode ler/escrever
    - Requer cuidado com concorrência
    - Útil para tabelas compartilhadas
```

#### 3.3 Gerenciamento de Ciclo de Vida

```elixir
defmodule TableLifecycle do
  # Criar tabela com herdeiro
  def create_with_heir do
    parent = self()
    
    :ets.new(:tabela_com_herdeiro, [
      :set,
      :public,
      :named_table,
      {:heir, parent, :dados_salvos}  # Se o criador morrer, parent recebe a tabela
    ])
  end
  
  # Aguardar herança
  def wait_for_inheritance do
    receive do
      {:inherited_table, :tabela_com_herdeiro, :dados_salvos} ->
        IO.puts("Recebi a tabela do processo que morreu")
    end
  end
  
  # Validar se tabela existe
  def table_exists?(table_name) do
    case :ets.whereis(table_name) do
      :undefined -> false
      _ -> true
    end
  end
  
  # Obter informações da tabela
  def table_info(table_name) do
    %{
      size: :ets.info(table_name, :size),
      memory: :ets.info(table_name, :memory),  # Words (4 bytes)
      type: :ets.info(table_name, :type),
      keypos: :ets.info(table_name, :keypos),
      owner: :ets.info(table_name, :owner),
      name: :ets.info(table_name, :name)
    }
  end
  
  # Deletar tabela explicitamente
  def delete_table(table_name) do
    :ets.delete(table_name)  # Remove todos os dados e a tabela
  end
  
  # Limpar dados sem deletar tabela
  def clear_table(table_name) do
    :ets.delete_all_objects(table_name)  # Remove todos registros
  end
end
```

***

### 4. OPERAÇÕES BÁSICAS: CRUD

#### 4.1 Insert e Update

```elixir
defmodule CrudEts do
  # Inserir um registro
  def insert(table, key, value) do
    :ets.insert(table, {key, value})
    :ok
  end
  
  # Inserir múltiplos registros
  def batch_insert(table, list) do
    :ets.insert(table, list)
    length(list)
  end
  
  # Inserir novo se não existir (emulando "insert_new")
  def insert_new(table, key, value) do
    :ets.insert_new(table, {key, value})  # Retorna true se inseriu, false se já existia
  end
  
  # Atualização condicional
  def update_if_exists(table, key, updater) do
    case :ets.lookup(table, key) do
      [] -> {:error, :not_found}
      [entry] ->
        new_entry = updater.(entry)
        :ets.insert(table, new_entry)
        {:ok, new_entry}
    end
  end
  
  # Upsert (insert or update)
  def upsert_counter(table, key, increment) do
    :ets.update_counter(table, key, increment)
  end
  
  # Exemplo prático
  def example do
    table = :ets.new(:exemplo, [:set, :public])
    
    # Insert
    :ets.insert(table, {:contador, 0})
    
    # Update (increment)
    new_value = :ets.update_counter(table, :contador, {2, 1})
    # {2, 1} significa: posição 2 do registro, incrementa 1
    
    # Insert_new (só se não existe)
    :ets.insert_new(table, {:contador, 0})  # false (já existe)
    
    # Batch insert
    :ets.insert(table, [
      {:user, 1, "João"},
      {:user, 2, "Maria"},
      {:user, 3, "Pedro"}
    ])
  end
end
```

#### 4.2 Lookup e Leitura

```elixir
defmodule ReadEts do
  # Leitura direta por chave
  def lookup(table, key) do
    :ets.lookup(table, key)
  end
  
  # Leitura com fallback
  def lookup_with_default(table, key, default) do
    case :ets.lookup(table, key) do
      [] -> default
      [value] -> value
    end
  end
  
  # Leitura de elemento específico do registro
  def lookup_element(table, key, position) do
    # Retorna o elemento na posição 'position' do registro
    :ets.lookup_element(table, key, position)
  rescue
    ArgumentError -> nil
    BadargError -> nil
  end
  
  # Verificar existência (mais rápido que lookup)
  def member?(table, key) do
    :ets.member(table, key)
  end
  
  # Leitura de todos registros (CUIDADO!)
  def read_all(table) do
    :ets.tab2list(table)  # Retorna lista completa
  end
  
  # Leitura com match parcial
  def match(table, pattern) do
    # Pattern usa '$1', '$2' como placeholders
    :ets.match(table, pattern)
  end
  
  # Exemplo
  def example do
    table = :ets.new(:exemplo, [:set])
    :ets.insert(table, {:user, 1, "João", 30})
    
    # lookup
    :ets.lookup(table, :user)  # [{:user, 1, "João", 30}]
    
    # lookup_element (posição 3 é "João")
    :ets.lookup_element(table, :user, 3)  # "João"
    
    # member
    :ets.member(table, :user)  # true
    :ets.member(table, :inexistente)  # false
    
    # match pattern (pega nome e idade)
    :ets.match(table, {:user, :_ , :"$1", :"$2"})  # [["João", 30]]
  end
end
```

#### 4.3 Delete e Limpeza

```elixir
defmodule DeleteEts do
  # Deletar por chave
  def delete(table, key) do
    :ets.delete(table, key)
  end
  
  # Deletar múltiplas chaves
  def delete_many(table, keys) do
    Enum.each(keys, fn key -> :ets.delete(table, key) end)
  end
  
  # Deletar registro específico (match completo)
  def delete_object(table, object) do
    :ets.delete_object(table, object)
  end
  
  # Deletar objetos por padrão
  def match_delete(table, pattern) do
    # Ex: :ets.match_delete(table, {:user, :_, "João", :_})
    :ets.match_delete(table, pattern)
  end
  
  # Limpar tabela (mantém estrutura)
  def clear(table) do
    :ets.delete_all_objects(table)
  end
  
  # Deletar tabela completamente
  def destroy(table) do
    :ets.delete(table)
  end
  
  # Exemplo
  def example do
    table = :ets.new(:exemplo, [:set])
    
    # Inserir dados
    :ets.insert(table, [
      {:user, 1, "João", 30},
      {:user, 2, "Maria", 25},
      {:user, 3, "Pedro", 35}
    ])
    
    # Delete por chave
    :ets.delete(table, :user)  # Deleta o registro com chave :user (todos!)
    # CUIDADO! Isso deleta TODOS com chave :user
    
    # Melhor: usar ID na chave
    :ets.insert(table, {1, "João", 30})
    :ets.insert(table, {2, "Maria", 25})
    
    # Delete específico por ID
    :ets.delete(table, 1)  # Só deleta João
    
    # Delete por object match
    :ets.delete_object(table, {2, "Maria", 25})  # Mesmo efeito
    
    # Match delete (apaga todos com idade > 30)
    :ets.match_delete(table, {:_, :_, :"$1"})  # Pattern mais complexo
  end
end
```

***

### 5. CONSULTAS E PATTERN MATCHING

#### 5.1 Match Specifications

Match specifications são a forma mais poderosa (e complexa) de consultar ETS.

```elixir
defmodule MatchSpecs do
  @moduledoc """
  Demonstra uso de match specifications para consultas avançadas.
  """
  
  # Match spec básica: {MatchHead, [Guard], [Result]}
  
  # 1. MatchHead - padrão a ser casado
  # 2. Guard - condições opcionais (booleanas)
  # 3. Result - o que retornar
  
  # Selecionar todos registros
  def match_all(table) do
    # '_' casa com qualquer termo
    :ets.match(table, {:user, :_, :_, :_})
  end
  
  # Selecionar com condição de guard
  def match_older_than(table, min_age) do
    # Pattern: {:user, id, name, age}
    # Guard: age >= min_age
    # Return: {name, age}
    match_spec = [
      {{:user, :"$1", :"$2", :"$3"},
       [{:>=, :"$3", min_age}],
       [{{:"$2", :"$3"}}]}
    ]
    
    :ets.select(table, match_spec)
  end
  
  # Match com múltiplas condições
  def match_specific(table, name_pattern, min_age) do
    match_spec = [
      {{:user, :"$1", :"$2", :"$3"},
       [{:andalso, 
         {:>=, :"$3", min_age},
         {:is_atom, :"$2"}}],
       [:"$1"]}  # Return ID
    ]
    
    :ets.select(table, match_spec)
  end
  
  # Exemplo complexo
  def complex_query do
    table = :ets.new(:users, [:set])
    
    # Popula dados
    :ets.insert(table, [
      {1, "João", 30, "SP"},
      {2, "Maria", 25, "RJ"},
      {3, "Pedro", 35, "SP"},
      {4, "Ana", 28, "SP"}
    ])
    
    # Busca: usuários de SP com idade > 25
    # Retorna: {nome, idade}
    match_spec = [
      {{:"$1", :"$2", :"$3", "SP"},
       [{:>, :"$3", 25}],
       [{{:"$2", :"$3"}}]}
    ]
    
    :ets.select(table, match_spec)
    # Resultado: [{"João", 30}, {"Pedro", 35}]
  end
end
```

#### 5.2 Select com Funções de Guard

```elixir
defmodule GuardFunctions do
  # Guards com operadores de comparação
  def compare_guards(table) do
    # Busca usuários com idade entre 25 e 35
    spec = [
      {{:user, :"$1", :"$2", :"$3"},
       [{:andalso, 
         {:>=, :"$3", 25},
         {:<=, :"$3", 35}}],
       [:"$2"]}
    ]
    :ets.select(table, spec)
  end
  
  # Guards com type tests
  def type_guards(table) do
    spec = [
      {{:user, :"$1", :"$2", :"$3"},
       [{:is_integer, :"$1"}, {:is_binary, :"$2"}],
       [:"$1"]}
    ]
    :ets.select(table, spec)
  end
  
  # Guards com operações aritméticas
  def arithmetic_guards(table) do
    # Busca idade par
    spec = [
      {{:user, :"$1", :"$2", :"$3"},
       [{{:==, {:rem, :"$3", 2}, 0}}],
       [:"$2"]}
    ]
    :ets.select(table, spec)
  end
  
  # Guards com OR lógico
  def or_guards(table) do
    spec = [
      {{:user, :"$1", :"$2", :"$3"},
       [{:orelse, 
         {:"==", :"$3", 30},
         {:"==", :"$3", 35}}],
       [:"$2"]}
    ]
    :ets.select(table, spec)
  end
end
```

***

### 6. ITERAÇÃO E BUSCA AVANÇADA

#### 6.1 Fold e Iteração

```elixir
defmodule IterationEts do
  # Fold sobre tabela (acumulador)
  def fold_table(table, acc, fun) do
    :ets.foldl(fn element, acc -> fun.(element, acc) end, acc, table)
  end
  
  # Exemplo: calcular média de idades
  def average_age(table) do
    {sum, count} = :ets.foldl(
      fn {:user, _, _, age}, {sum, count} -> {sum + age, count + 1} end,
      {0, 0},
      table
    )
    
    if count > 0, do: sum / count, else: 0
  end
  
  # Filtrar com fold
  def filter_older_than(table, min_age) do
    :ets.foldl(
      fn {:user, id, name, age}, acc ->
        if age >= min_age, do: [{id, name, age} | acc], else: acc
      end,
      [],
      table
    )
    |> Enum.reverse()
  end
  
  # First/Next para iterar manualmente (útil para ordered_set)
  def iterate_ordered(table) do
    case :ets.first(table) do
      :"$end_of_table" ->
        :ok
      key ->
        iterate_from(table, key)
    end
  end
  
  defp iterate_from(table, key) do
    # Processa key
    value = :ets.lookup(table, key)
    IO.inspect({key, value})
    
    # Próxima chave
    case :ets.next(table, key) do
      :"$end_of_table" -> :ok
      next_key -> iterate_from(table, next_key)
    end
  end
  
  # Range query em ordered_set
  def range_query_ordered(table, from_key, to_key) do
    collect_range(table, from_key, to_key, [])
  end
  
  defp collect_range(table, current, to_key, acc) when current == to_key do
    acc
  end
  
  defp collect_range(table, current, to_key, acc) do
    value = :ets.lookup(table, current)
    case :ets.next(table, current) do
      :"$end_of_table" -> acc
      next_key -> collect_range(table, next_key, to_key, [value | acc])
    end
  end
end
```

#### 6.2 Info e Estatísticas

```elixir
defmodule EtsInfo do
  # Obter todas informações da tabela
  def full_info(table) do
    :ets.info(table)
  end
  
  # Estatísticas detalhadas
  def stats(table) do
    %{
      memory_bytes: :ets.info(table, :memory) * 4,  # Words para bytes
      size: :ets.info(table, :size),
      type: :ets.info(table, :type),
      owner_pid: :ets.info(table, :owner),
      read_concurrency: :ets.info(table, :read_concurrency),
      write_concurrency: :ets.info(table, :write_concurrency),
      compressed: :ets.info(table, :compressed),
      keypos: :ets.info(table, :keypos)
    }
  end
  
  # Monitorar crescimento da tabela
  def monitor_growth(table, interval_ms \\ 5000) do
    spawn(fn ->
      loop_monitor(table, interval_ms, :ets.info(table, :size))
    end)
  end
  
  defp loop_monitor(table, interval, prev_size) do
    Process.sleep(interval)
    current_size = :ets.info(table, :size)
    growth = current_size - prev_size
    
    IO.puts("Tabela #{inspect(table)}: #{current_size} registros (+#{growth})")
    loop_monitor(table, interval, current_size)
  end
end
```

***

### 7. ETS vs DETS vs MNESIA

#### 7.1 Comparação Detalhada

```yaml
COMPARAÇÃO ENTRE SISTEMAS DE ARMAZENAMENTO:

  ⚡ ETS (ERLANG TERM STORAGE):
    - Persistência: NÃO (volátil)
    - Distribuído: NÃO
    - Transações: NÃO
    - Velocidade: EXTREMAMENTE RÁPIDA
    - Concorrência: Sim (com opções)
    - Tamanho Máximo: Memória disponível
    - Uso Típico: Cache, lookup tables, counters

  💿 DETS (DISK ERLANG TERM STORAGE):
    - Persistência: SIM (disco)
    - Distribuído: NÃO
    - Transações: SIM (limitadas)
    - Velocidade: LENTA (disco)
    - Concorrência: Limitada
    - Tamanho Máximo: 2GB
    - Uso Típico: Backup de ETS, dados persistentes pequenos

  🌐 MNESIA (DISTRIBUTED DBMS):
    - Persistência: SIM (configurável)
    - Distribuído: SIM
    - Transações: SIM (ACID)
    - Velocidade: MÉDIA (overhead de transações)
    - Concorrência: Excelente
    - Tamanho Máximo: Praticamente ilimitado (sharding)
    - Uso Típico: Dados críticos, sistemas distribuídos
```

#### 7.2 Exemplo: ETS como Cache + DETS para Persistência

```elixir
defmodule CacheWithPersist do
  @moduledoc """
  Combina ETS (cache rápido) com DETS (persistência em disco).
  """
  
  def start do
    # Abre tabela DETS (disco)
    {:ok, dets_ref} = :dets.open_file(:persist_cache, [
      type: :set,
      auto_save: 5000
    ])
    
    # Cria ETS (memória)
    ets_ref = :ets.new(:cache, [:set, :public])
    
    # Carrega dados do DETS para ETS
    :dets.foldl(fn record, _ ->
      :ets.insert(ets_ref, record)
    end, [], dets_ref)
    
    {ets_ref, dets_ref}
  end
  
  def put({ets_ref, dets_ref}, key, value) do
    # Escreve em ambos
    :ets.insert(ets_ref, {key, value})
    :dets.insert(dets_ref, {key, value})
  end
  
  def get({ets_ref, _dets_ref}, key) do
    # Lê do ETS (rápido)
    case :ets.lookup(ets_ref, key) do
      [] -> nil
      [{^key, value}] -> value
    end
  end
  
  def sync_to_disk({_ets_ref, dets_ref}) do
    # Sincroniza DETS com disco
    :dets.sync(dets_ref)
  end
  
  def stop({_ets_ref, dets_ref}) do
    # Fecha DETS (libera locks)
    :dets.close(dets_ref)
  end
end
```

***

### 8. PERFORMANCE E OTIMIZAÇÃO

#### 8.1 Benchmarks e Comparações

```elixir
defmodule EtsBenchmark do
  @moduledoc """
  Mede performance de diferentes tipos de tabela.
  """
  
  def benchmark_set_vs_ordered_set do
    set_table = :ets.new(:set_test, [:set])
    ordered_table = :ets.new(:ordered_test, [:ordered_set])
    
    # Insere 100k registros
    data = Enum.map(1..100_000, fn i -> {i, "valor_#{i}"} end)
    
    # Benchmark insert
    {set_insert, _} = :timer.tc(fn ->
      Enum.each(data, fn record -> :ets.insert(set_table, record) end)
    end)
    
    {ordered_insert, _} = :timer.tc(fn ->
      Enum.each(data, fn record -> :ets.insert(ordered_table, record) end)
    end)
    
    # Benchmark lookup
    {set_lookup, _} = :timer.tc(fn ->
      Enum.each(1..100_000, fn i -> :ets.lookup(set_table, i) end)
    end)
    
    {ordered_lookup, _} = :timer.tc(fn ->
      Enum.each(1..100_000, fn i -> :ets.lookup(ordered_table, i) end)
    end)
    
    %{
      set: %{insert: set_insert / 1000, lookup: set_lookup / 1000},
      ordered_set: %{insert: ordered_insert / 1000, lookup: ordered_lookup / 1000}
    }
  end
  
  def benchmark_concurrency(table_type) do
    table = :ets.new(:concurrency_test, [table_type, :public])
    
    # Escritores concorrentes
    writer_task = fn ->
      1..1000 |> Enum.each(fn i -> :ets.insert(table, {i, i}) end)
    end
    
    # Leitores concorrentes
    reader_task = fn ->
      1..1000 |> Enum.each(fn i -> :ets.lookup(table, i) end)
    end
    
    # 10 escritores concorrentes
    {write_time, _} = :timer.tc(fn ->
      1..10 |> Enum.map(fn _ -> Task.async(writer_task) end) |> Enum.each(&Task.await/1)
    end)
    
    # 10 leitores concorrentes
    {read_time, _} = :timer.tc(fn ->
      1..10 |> Enum.map(fn _ -> Task.async(reader_task) end) |> Enum.each(&Task.await/1)
    end)
    
    %{write_time: write_time / 1000, read_time: read_time / 1000}
  end
end
```

#### 8.2 Dicas de Performance

```yaml
OTIMIZAÇÕES DE PERFORMANCE NO ETS:

  📊 ESTRATÉGIAS DE TABELA:
    ✅ Use :set para acesso direto por chave (mais rápido)
    ✅ Use :ordered_set apenas se precisar de range queries
    ✅ Prefira :public para tabelas de leitura/multi-escrita
    ✅ Use :protected para tabelas de leitura intensiva

  ⚙️ OPÇÕES DE CRIAÇÃO:
    ✅ {:write_concurrency, true} para muitos escritores
    ✅ {:read_concurrency, true} para muitos leitores
    ✅ {:compressed, true} para economizar memória (com custo CPU)
    ✅ {:size, N} para pré-alocar espaço (evita realocações)

  🔍 CONSULTAS:
    ✅ Use :ets.lookup/2 para acesso por chave (mais rápido)
    ✅ Use :ets.select/2 para queries complexas
    ✅ Evite :ets.tab2list/1 em tabelas grandes
    ✅ Prefira :ets.foldl/3 para acumulação

  💾 MEMÓRIA:
    ✅ Registros pequenos são mais eficientes
    ✅ Use átomos em vez de strings para valores constantes
    ✅ Binários grandes são referenciados (não copiados)
    ✅ Monitore uso de memória com :ets.info/1
```

#### 8.3 Exemplo: Cache Otimizado

```elixir
defmodule OptimizedCache do
  @moduledoc """
  Cache ETS otimizado com TTL e limites de tamanho.
  """
  
  defstruct [:table, :max_size, :ttl_ms]
  
  def start(max_size \\ 10_000, ttl_ms \\ 60_000) do
    table = :ets.new(:cache, [
      :set,
      :public,
      {:write_concurrency, true},
      {:read_concurrency, true},
      {:size, max_size}
    ])
    
    # Monitor de limpeza assíncrono
    spawn(fn -> cleaner_loop(table, ttl_ms) end)
    
    %__MODULE__{table: table, max_size: max_size, ttl_ms: ttl_ms}
  end
  
  def put(cache, key, value) do
    # Verifica tamanho
    if :ets.info(cache.table, :size) >= cache.max_size do
      evict_oldest(cache.table)
    end
    
    # Armazena com timestamp
    :ets.insert(cache.table, {key, value, :erlang.system_time(:millisecond)})
  end
  
  def get(%{table: table}, key) do
    case :ets.lookup(table, key) do
      [] -> nil
      [{^key, value, ts}] ->
        # Retorna valor mesmo se expirado (cleaner vai remover)
        value
    end
  end
  
  defp evict_oldest(table) do
    # Remove registro mais antigo (implementação simplificada)
    case :ets.first(table) do
      :"$end_of_table" -> :ok
      key -> :ets.delete(table, key)
    end
  end
  
  defp cleaner_loop(table, ttl_ms) do
    Process.sleep(ttl_ms / 2)  # Limpa a cada meio TTL
    
    now = :erlang.system_time(:millisecond)
    expired = :erlang.system_time(:millisecond) - ttl_ms
    
    # Match specification para encontrar expirados
    spec = [
      {{:"$1", :"$2", :"$3"},
       [{:<, :"$3", expired}],
       [:"$1"]}
    ]
    
    expired_keys = :ets.select(table, spec)
    Enum.each(expired_keys, fn key -> :ets.delete(table, key) end)
    
    cleaner_loop(table, ttl_ms)
  end
end
```

***

### 9. CONCORRÊNCIA E ACESSO MULTI-PROCESSO

#### 9.1 Acesso Concorrente com GenServer

```elixir
defmodule SafeTable do
  @moduledoc """
  Wrapper GenServer para acesso seguro a ETS.
  """
  
  use GenServer
  
  # Client API
  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end
  
  def put(key, value) do
    GenServer.call(__MODULE__, {:put, key, value})
  end
  
  def get(key) do
    GenServer.call(__MODULE__, {:get, key})
  end
  
  def delete(key) do
    GenServer.call(__MODULE__, {:delete, key})
  end
  
  # Server Callbacks
  def init(_opts) do
    table = :ets.new(:safe_table, [:set, :protected])
    {:ok, %{table: table}}
  end
  
  def handle_call({:put, key, value}, _from, state) do
    :ets.insert(state.table, {key, value})
    {:reply, :ok, state}
  end
  
  def handle_call({:get, key}, _from, state) do
    result = case :ets.lookup(state.table, key) do
      [] -> nil
      [{^key, value}] -> value
    end
    {:reply, result, state}
  end
  
  def handle_call({:delete, key}, _from, state) do
    :ets.delete(state.table, key)
    {:reply, :ok, state}
  end
end
```

#### 9.2 O Problema das Condições de Corrida

```elixir
defmodule RaceCondition do
  @moduledoc """
  Demonstra problema de race condition em ETS público.
  """
  
  # ❌ PROBLEMA: incremento não atômico
  def bad_increment(table, key) do
    value = case :ets.lookup(table, key) do
      [] -> 0
      [{^key, v}] -> v
    end
    
    # Race condition aqui! Outro processo pode ter mudado o valor
    :ets.insert(table, {key, value + 1})
  end
  
  # ✅ SOLUÇÃO: uso de update_counter (atômico)
  def good_increment(table, key) do
    :ets.update_counter(table, key, {2, 1})  # Incrementa posição 2 do registro
  end
  
  # ✅ SOLUÇÃO: transação com GenServer (serializado)
  def safe_increment(table, key) do
    # Serializa acesso via GenServer (ver exemplo anterior)
    GenServer.call(:safe_counter, {:increment, table, key})
  end
  
  # ✅ SOLUÇÃO: tabela privada (só um processo escreve)
  def private_table_increment do
    table = :ets.new(:contador, [:set, :private])
    :ets.insert(table, {:counter, 0})
    
    # Apenas este processo escreve, então é seguro
    :ets.update_counter(table, :counter, {2, 1})
  end
end
```

***

### 10. MATCH SPECIFICATIONS AVANÇADAS

#### 10.1 Match Specifications Completas

```elixir
defmodule AdvancedMatchSpec do
  @moduledoc """
  Match specifications avançadas para consultas complexas.
  """
  
  # Exemplo: Match spec com transformação de dados
  def transform_example do
    table = :ets.new(:users, [:set])
    :ets.insert(table, [
      {1, "João", 30, "SP", :ativo},
      {2, "Maria", 25, "RJ", :inativo},
      {3, "Pedro", 35, "SP", :ativo}
    ])
    
    # Retorna nome em maiúsculo, idade, e status como string
    spec = [
      {{:"$1", :"$2", :"$3", :"$4", :"$5"},
       [{:andalso, 
         {:is_atom, :"$5"},
         {:==, :"$5", :ativo}}],
       [{:string, :to_upper, :"$2"}, :"$3", {:atom_to_list, :"$5"}]}
    ]
    
    :ets.select(table, spec)
    # Resultado: [{"JOÃO", 30, "ativo"}, {"PEDRO", 35, "ativo"}]
  end
  
  # Match spec com funções personalizadas
  def custom_function_example do
    # Defina função no módulo
    def is_adult(age), do: age >= 18
    
    table = :ets.new(:people, [:set])
    :ets.insert(table, [
      {1, "João", 16},
      {2, "Maria", 25},
      {3, "Pedro", 30}
    ])
    
    spec = [
      {{:"$1", :"$2", :"$3"},
       [{:call, {AdvancedMatchSpec, :is_adult, [:"$3"]}}],
       [:"$2"]}
    ]
    
    :ets.select(table, spec)  # ["Maria", "Pedro"]
  end
  
  # Match spec com ordenação
  def sorted_example do
    table = :ets.new(:scores, [:set])
    :ets.insert(table, [
      {:player1, 100},
      {:player2, 250},
      {:player3, 150}
    ])
    
    spec = [
      {:"$1", :"$2"},
      [],
      [{{:"$1", :"$2"}}]
    ]
    
    # Select com ordenação
    :ets.select(table, spec, 10)
    # Retorna {list, continuation}
  end
end
```

#### 10.2 Funções Úteis em Match Spec

```yaml
FUNÇÕES DISPONÍVEIS EM GUARDS MATCH SPEC:

  COMPARAÇÃO:
    :==, :/=, :<, :>, :>=, :<=
    Ex: {:>=, :"$1", 18}

  ARITMÉTICAS:
    :+, :-, :*, :/, :rem, :div
    Ex: {{:+, :"$1", 10}}

  TYPE TESTS:
    :is_atom, :is_integer, :is_float, :is_binary, :is_list, :is_tuple
    Ex: {:is_atom, :"$1"}

  LÓGICAS:
    :andalso, :orelse, :not
    Ex: {:andalso, cond1, cond2}

  CHAMADA DE FUNÇÃO:
    :call
    Ex: {:call, {Module, :function, [arg]}}

  MATCH DE STRINGS:
    :is_prefix, :is_suffix, :is_sub_string
    Ex: {:is_sub_string, "João", :"$2"}
```

***

### 11. BOAS PRÁTICAS E PADRÕES COMUNS

#### 11.1 Padrão: Cache com CQRS

```elixir
defmodule CommandQueryCache do
  @moduledoc """
  Separa comandos (escrita) de queries (leitura) com ETS.
  """
  
  defmodule Query do
    def start do
      table = :ets.new(:query_cache, [
        :set,
        :protected,
        {:read_concurrency, true}
      ])
      
      # Preenche cache inicial
      preload_cache(table)
      
      table
    end
    
    def get(table, key) do
      case :ets.lookup(table, key) do
        [] -> {:error, :not_found}
        [{^key, value}] -> {:ok, value}
      end
    end
    
    defp preload_cache(table) do
      # Carrega dados do banco real
      # ... implementation
    end
  end
  
  defmodule Command do
    def start do
      table = :ets.new(:command_store, [
        :set,
        :private,
        {:write_concurrency, true}
      ])
      
      table
    end
    
    def write(table, key, value) do
      :ets.insert(table, {key, value})
      # Notifica query cache para atualizar
      # ... broadcast
    end
  end
end
```

#### 11.2 Padrão: Counter de Alta Performance

```elixir
defmodule HighPerfCounters do
  @moduledoc """
  Counters otimizados usando ETS update_counter.
  """
  
  def start do
    table = :ets.new(:counters, [
      :set,
      :public,
      {:write_concurrency, true}
    ])
    
    table
  end
  
  def increment(table, key, amount \\ 1) do
    # Atômico e sem transação
    :ets.update_counter(table, key, {2, amount}, {key, 0})
  end
  
  def get(table, key) do
    case :ets.lookup(table, key) do
      [] -> 0
      [{^key, value}] -> value
    end
  end
  
  def get_all(table) do
    :ets.tab2list(table)
  end
  
  def reset(table, key) do
    :ets.insert(table, {key, 0})
  end
  
  # Batch increment (múltiplos counters)
  def batch_increment(table, increments) do
    # Lista de {key, amount}
    Enum.each(increments, fn {key, amount} ->
      increment(table, key, amount)
    end)
  end
end
```

#### 11.3 Padrão: LRU Cache

```elixir
defmodule LRUCache do
  @moduledoc """
  Cache com política Least Recently Used usando ETS.
  """
  
  defstruct [:table, :max_size, :order_table]
  
  def start(max_size \\ 1000) do
    # Tabela de dados
    data_table = :ets.new(:lru_data, [:set, :public])
    
    # Tabela de ordem (ordered_set para range queries)
    order_table = :ets.new(:lru_order, [:ordered_set, :public])
    
    %__MODULE__{
      table: data_table,
      max_size: max_size,
      order_table: order_table
    }
  end
  
  def put(cache, key, value) do
    now = :erlang.system_time(:millisecond)
    
    # Verifica tamanho
    if :ets.info(cache.table, :size) >= cache.max_size do
      evict_lru(cache)
    end
    
    # Atualiza dados
    :ets.insert(cache.table, {key, value})
    
    # Atualiza ordem (remove antigo, adiciona novo)
    :ets.delete(cache.order_table, key)
    :ets.insert(cache.order_table, {key, now})
  end
  
  def get(cache, key) do
    case :ets.lookup(cache.table, key) do
      [] -> nil
      [{^key, value}] ->
        # Update access time
        touch(cache, key)
        value
    end
  end
  
  defp touch(cache, key) do
    now = :erlang.system_time(:millisecond)
    :ets.insert(cache.order_table, {key, now})
  end
  
  defp evict_lru(cache) do
    # Encontra chave mais antiga
    case :ets.first(cache.order_table) do
      :"$end_of_table" -> :ok
      key ->
        :ets.delete(cache.order_table, key)
        :ets.delete(cache.table, key)
    end
  end
end
```

***

### 12. RESUMO TÉCNICO

```yaml
RESUMO DO ETS:

  🎯 QUANDO USAR:
    ✅ Cache de dados de alta performance
    ✅ Lookup tables (ex: dicionários)
    ✅ Counters e acumuladores atômicos
    ✅ Dados compartilhados entre processos
    ✅ Configuração de runtime
    ✅ Pool de recursos (conexões, workers)

  ❌ QUANDO NÃO USAR:
    ❌ Dados que precisam persistir (use DETS/Mnesia)
    ❌ Transações ACID (use Mnesia)
    ❌ Dados muito grandes (>RAM disponível)
    ❌ Distribuição entre nós (use Mnesia)
    ❌ Queries analíticas complexas

  🔄 ALTERNATIVAS:
    • DETS: versão em disco do ETS
    • Mnesia: banco distribuído com persistência
    • Processes: para dados locais do processo
    • Redis: cache distribuído externo

  📊 PERFORMANCE TÍPICA:
    • Lookup: 100-500 ns (set), 1-2 µs (ordered_set)
    • Insert: 200-800 ns (set), 2-5 µs (ordered_set)
    • Memory: 40-80 bytes/registro (overhead)
```

***

### 13. REFERÊNCIA RÁPIDA

#### 13.1 Comandos Essenciais

| Operação           | Comando                 | Complexidade               |
| ------------------ | ----------------------- | -------------------------- |
| Criar tabela       | `:ets.new/2`            | O(1)                       |
| Inserir            | `:ets.insert/2`         | O(1) set, O(log n) ordered |
| Buscar por chave   | `:ets.lookup/2`         | O(1) set, O(log n) ordered |
| Delete por chave   | `:ets.delete/2`         | O(1) set, O(log n) ordered |
| Incremento atômico | `:ets.update_counter/3` | O(1) set, O(log n) ordered |
| Select             | `:ets.select/2`         | O(n)                       |
| Fold               | `:ets.foldl/3`          | O(n)                       |
| Info               | `:ets.info/1`           | O(1)                       |

#### 13.2 Opções Comuns

```elixir
# Opções de tipo
:set              # Hash table (padrão)
:ordered_set      # Árvore ordenada
:bag             # Multiplas chaves
:duplicate_bag   # Bag otimizado

# Opções de acesso
:private         # Apenas criador
:protected       # Leitura pública (padrão)
:public          # Leitura/escrita pública

# Opções de performance
{:write_concurrency, true}  # Múltiplos escritores
{:read_concurrency, true}   # Múltiplos leitores
{:compressed, true}         # Comprime dados
{:size, N}                  # Pré-aloca N posições

# Opções de chave
{:keypos, N}      # Posição da chave no registro
```

#### 13.3 Padrões de Match Spec

```elixir
# Básico
[{MatchHead, [], [Return]}]

# Com guard
[{MatchHead, [Guard], [Return]}]

# Exemplo prático
spec = [
  {{:user, :"$1", :"$2", :"$3"},
   [{:>=, :"$3", 18}],
   [{{:name, :"$2"}}]}
]
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xmorte.gitbook.io/bibliadopentestbr/conceitos/programacao-e-linguagens/elixir/9.-ets-erlang-term-storage.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
