Estratégia de amostragem
Estratégias de amostragem cobre o assunto do ponto de vista de quem roda o wizard: quando escolher cada uma, e o trade-off de custo ou viés envolvido. Esta página cobre a Porta em si, como cada Extrator traduz a mesma política em SQL real, e por que essa tradução diverge tanto entre Postgres e MariaDB.
O Port não sabe gerar SQL
EstrategiaDeAmostragem (domain/ports/estrategia_de_amostragem.py) expõe só duas
propriedades: nome, o identificador gravado em MetadadosDeAmostra.estrategia, e
requisicao, um RequisicaoDeAmostragem (AmostragemProbabilistica, AmostragemIntegral
ou RequisicaoPorFaixa) que descreve quanto amostrar. Nenhum método da Porta produz
SQL, TABLESAMPLE, WHERE ou qualquer outro mecanismo concreto. A própria docstring da
Porta explica o motivo dado o fato que traduzir a política em consulta concreta cabe a cada Extrator, já
acoplado ao dialeto da própria fonte, para não amarrar a política de amostragem a nenhum
banco específico. Custo de execução também é responsabilidade de cada Extrator, não da
Porta: a Porta descreve só "quanto", nunca "quão caro" é fazer isso num motor específico.
Seed: reprodutibilidade fixa, não aleatoriedade reprodutível
seed_efetivo (extractors/comum/seed_efetivo.py) é o único ponto que decide qual seed
cada Extrator usa de fato, e os dois motores passam por ele antes de montar a query, tanto
para AmostragemProbabilistica quanto para RequisicaoPorFaixa. Quando o usuário não
informa um seed, a função não sorteia um valor novo por execução, ele devolve uma constante
fixa (1) sendo a mesma para qualquer tabela, em qualquer execução, em qualquer motor.
Sem seed explícito, o ddf sempre
lê a mesma fatia da tabela. A consequência é dupla. A favor, isso dá diff estável em Git
entre execuções sucessivas visto que passa a ser a mesma amostra, as mesmas linhas, sem o artefato mudar por ruído de
amostragem que não reflete nenhuma mudança real na fonte. Contra, se essa fatia fixa calhar
de não ser representativa da tabela, o viés nunca é percebido, porque a amostra nunca varia
entre execuções para expor a diferença. Rotacionar a amostra fica por conta do usuário,
passando um seed explícito diferente a cada vez que quiser uma fatia nova.
O seed realmente usado, explícito ou o default, é gravado em MetadadosDeAmostra.seed e
não só consumido na query e descartado. Uma amostra antiga sempre pode ser reproduzida a
partir do artefato gerado, mesmo que ninguém lembre se um seed foi passado na hora.
Três políticas, três tipos de requisição
| Estratégia | requisicao |
O que descreve |
|---|---|---|
PercentualDeLinhas |
AmostragemProbabilistica(percentual, seed) |
Uma fração das linhas, decidida por um mecanismo probabilístico do próprio motor. |
TabelaInteira |
AmostragemIntegral() |
Leitura completa, sem mecanismo de amostragem. |
AmostragemPorFaixa |
RequisicaoPorFaixa(percentual, seed) |
Uma fração das linhas, lida por faixas contíguas em vez de linha a linha. |
TabelaInteira não é só PercentualDeLinhas(percentual=100) com outro nome: o resultado
prático das duas é equivalente, mas cada Extrator monta um SELECT * puro para
AmostragemIntegral, sem passar pelo mecanismo probabilístico do motor. Isso deixa a
intenção "quero a tabela inteira" explícita em metadados_amostra.estrategia, em vez de
depender de quem lê o artefato saber que percentual=100 produz o mesmo resultado.
Postgres: um mecanismo nativo por caso
montar_consulta_amostra (extractors/postgres/_construcao.py) monta uma única query por
tipo de requisicao, todas variações do mesmo SELECT * FROM schema.tabela ...:
AmostragemProbabilisticaviraTABLESAMPLE BERNOULLI (percentual) REPEATABLE (seed): o Postgres avalia uma moeda com viés por linha varrida, o que exige varrer a tabela inteira antes de decidir quais linhas entram, independente dopercentualpedido.AmostragemIntegralvira umSELECT *sem cláusula de amostragem.RequisicaoPorFaixaviraTABLESAMPLE SYSTEM (percentual) REPEATABLE (seed): o Postgres sorteia blocos de 8KB inteiros em vez de linhas individuais, então blocos fora do sorteio nunca são lidos. É esse mecanismo que faz o custo deAmostragemPorFaixaescalar com opercentualpedido, não com o total de linhas da tabela, e é também a origem do viés de cluster: linhas do mesmo bloco físico tendem a ter sido escritas na mesma janela de tempo.
Os dois mecanismos existem prontos no Postgres, funcionam sobre qualquer tabela, e não
dependem da forma da chave primária. seed_efetivo preenche um valor determinístico
quando o usuário não informa um, e esse valor é devolvido para extrair_tabela gravar o
seed realmente usado nos metadados da amostra, não só na query executada.
MariaDB: sem TABLESAMPLE, aproximação construída no Extrator
O MariaDB não tem equivalente a TABLESAMPLE. ExtratorMariaDB.extrair_tabela monta a
query de amostra na própria etapa de leitura, com um mecanismo diferente por caso:
AmostragemProbabilisticaviraWHERE RAND(seed) <= percentual / 100: mesma característica de custo doBERNOULLIdo Postgres, uma varredura completa que avalia a condição linha a linha, independente dopercentualpedido.AmostragemIntegralvira umSELECT *sem cláusula de amostragem.RequisicaoPorFaixanão tem mecanismo nativo nenhum para reaproveitar. Oddfconstrói uma aproximação a partir do domínio da chave primária:- Elegibilidade:
_elegibilidade_de_pk_para_faixasó aceita PK de coluna única e tipo inteiro (tinyint/smallint/mediumint/int/bigint), os únicos tipos ondeMAX(pk)e um cortepk >= valorfazem sentido aritmético. PK composta, ausente ou de outro tipo (UUID,VARCHAR,DATE...) não serve. Nesse caso, a leitura cai para o mesmoWHERE RAND(seed) <= percentual / 100deAmostragemProbabilistica, com umAvisonomeando o motivo do fallback. - Quando elegível:
_k_faixas_paradecide quantas faixas sortear (mínimo 10, crescendo até 50 conforme o total de linhas passa de 1 milhão), para aproximar o espalhamento de blocos doTABLESAMPLE SYSTEMdo Postgres sem uma consulta por linha amostrada. Uma únicaSELECT MAX(pk) FROM tabelaestabelece o teto do domínio, e oskcortes são sorteados no lado do Python (random.Random(seed)), não viaRAND()dentro doWHERE:RAND(seed)no MariaDB é reavaliado a cada linha varrida pelo motor, o que faria o corte derivar sistematicamente para o início do intervalo de PK, em vez de sortear um ponto fixo. A consulta final é umUNION ALLdeksubconsultas, cada umaWHERE pk >= corte ORDER BY pk LIMIT linhas_por_faixa. - Quando a amostra retornada vem com menos da metade das linhas pedidas, o
ddfemite umAvisoapontando gaps densos na chave primária logo após os pontos sorteados como causa provável.
- Elegibilidade:
Onde isso cruza com paralelismo intra-tabela
A leitura paralela intra-tabela também particiona por faixa (ctid no Postgres, faixa de
PK no MariaDB), mas resolve um problema diferente: dividir a leitura de uma tabela grande
entre várias conexões, não decidir quanto amostrar. Hoje ela reaproveita a mesma checagem
de elegibilidade de PK do MariaDB descrita acima, mas é restrita a AmostragemIntegral (a
combinação de faixas de amostragem com faixas de paralelismo não é suportada nesta
versão). O mecanismo de particionamento em si, os números de paralelismo medidos e a
ausência de pg_export_snapshot no MariaDB estão detalhados em
Pipeline e paralelismo.