Skip to content

fix(articlemeta): corrige memory leak por lru_cache em instância e robustez dos formatters journal/issue#1424

Merged
robertatakenaka merged 2 commits intoscieloorg:mainfrom
robertatakenaka:fix_issue_and_journal_articlemeta_format
Mar 26, 2026
Merged

fix(articlemeta): corrige memory leak por lru_cache em instância e robustez dos formatters journal/issue#1424
robertatakenaka merged 2 commits intoscieloorg:mainfrom
robertatakenaka:fix_issue_and_journal_articlemeta_format

Conversation

@robertatakenaka
Copy link
Copy Markdown
Member

O que esse PR faz?

Refatora os formatters ArticleMeta de journal e issue, eliminando problemas de memory leak causados pelo uso de @lru_cache em métodos de instância, melhorando a robustez contra valores None e simplificando a legibilidade geral do código.

Principais mudanças:

Memory leak — lru_cache em instância:
Substitui todos os usos de @lru_cache por cache manual via atributo privado (_medline_titles, _titles_in_database_medline_secs, etc.). O lru_cache aplicado a métodos de instância retém self como chave do cache no nível da classe, impedindo o garbage collector de liberar as instâncias — o que em contexto de request Django/Gunicorn causa crescimento contínuo de memória.

Queryset lazy no Issue formatter:
O queryset Article.objects.filter(issue=self.obj, journal=self.journal) era executado no __init__, mesmo quando o dado não era necessário. Agora é uma @property lazy (article_qs) que só dispara a query quando acessada. Além disso, _format_article_info usava self.obj.article_set.count() (sem filtro por journal) para a contagem, divergindo do queryset filtrado — corrigido para usar self.article_qs.count().

Robustez contra None:

  • _format_field_use_system: guard clause para journal_acron antes de .upper(), e or '' para volume/number
  • _format_scielo_journal_info: journal_acron.upper() if journal_acron else None em v930
  • _format_code_sections: guard if not journal_toc: continue + select_related incluindo journal_toc
  • _format_issn_with_type: if issns antes de atribuir v435 para evitar lista vazia

Simplificações e limpeza:

  • _format_publisher_info: substitui loop com break por .first()
  • _format_contact_address_info: substitui try/except genérico por if address
  • _format_journal_history: early return, unifica blocos duplicados ADMITTED/INTERRUPTED
  • _format_indexing_info: loop único em vez de duas list comprehensions
  • _format_issn_info (issue): refatorado com early return para reduzir indentação
  • Renomeia _former_dict_journal_history_format_journal_history_entry
  • Renomeia key_to_issnkey_to_value
  • Remove comentários redundantes e normaliza whitespace

Sinalização de possível bug de negócio:
Adicionado TODO em _format_issn_type: quando issn_print == issn_scielo, o código original atribui 'ONLIN' ao v35, o que parece invertido. Mantido o comportamento original, mas marcado para revisão.

Onde a revisão poderia começar?

journal/formats/articlemeta_format.py — é a base (o issue formatter importa o journal formatter). As mudanças mais relevantes estão em:

  1. titles_in_database_medline_secs (padrão de cache manual)
  2. _format_journal_history (lógica de subfield_b simplificada)
  3. _format_issn_type (TODO sobre possível inversão de lógica)

Depois seguir para issue/formats/articlemeta_format.py, começando por article_qs e _format_issn_info.

Como este poderia ser testado manualmente?

  1. Gerar o output ArticleMeta para um journal e comparar campo a campo com o output da branch main — os valores devem ser idênticos (exceto se o bug de contagem em _format_article_info estava ativo, onde a contagem pode mudar para journals com múltiplos issues compartilhando artigos)
  2. Testar com journal/issue que tenha valores None em volume, number, journal_acron, journal_toc — a branch main pode lançar AttributeError ou TypeError, esta branch deve retornar resultado sem erro
  3. Monitorar consumo de memória do processo Gunicorn após múltiplos requests — o padrão de crescimento causado pelo lru_cache não deve mais ocorrer

Algum cenário de contexto que queira dar?

Este refactoring foi motivado pela investigação de consumo de memória em produção. O @lru_cache em métodos de instância é um antipattern conhecido em Python — o decorator armazena o bound method (que inclui self) como chave, efetivamente criando uma referência circular que impede o GC de coletar a instância. Em um servidor WSGI com muitos requests, isso causa leak progressivo.

As mudanças são conservadoras: nenhuma lógica de negócio foi alterada intencionalmente (com exceção da correção do queryset em _format_article_info). O TODO em _format_issn_type é uma sinalização para revisão futura, não uma mudança de comportamento.

Screenshots

N/A (refactoring interno sem impacto visual)

Quais são tickets relevantes?

Referências

…stez e legibilidade

- Substitui @lru_cache por cache manual em _titles_in_database_medline_secs
  para evitar memory leak (lru_cache em método de instância retém self
  indefinidamente no cache do decorador)
- Renomeia _medline_titles para _titles_in_database_medline_secs para
  consistência com o nome da property
- Renomeia _former_dict_journal_history para _format_journal_history_entry
  (corrige typo e segue convenção _format_* da classe)
- Renomeia variável key_to_issn para key_to_value (nem todos os valores
  são ISSNs)
- Adiciona guard clause 'journal_acron.upper() if journal_acron else None'
  em v930 para evitar AttributeError quando journal_acron é None
- Refatora _format_publisher_info: substitui loop com break por .first()
  para obter apenas o primeiro owner, eliminando iteração desnecessária
- Refatora _format_contact_address_info: substitui try/except genérico
  por checagem explícita 'if address', removendo tratamento silencioso
  de exceções
- Refatora _format_journal_history: inverte condicional para early return,
  unifica blocos ADMITTED/INTERRUPTED duplicados em um único 'if in',
  e garante que subfield_b é determinado por evento (não acumulado)
- Refatora _format_indexing_info: substitui duas list comprehensions
  separadas por um único loop para classificar medline vs secs,
  evitando dupla iteração sobre titles_in_db
- Refatora _format_collection_info: remove checagem redundante
  'if collection' (já coberta pelo 'if self.scielo_journal.collection')
- Remove guard desnecessário 'if self.official' em _format_issn_list
  (método só é chamado dentro de bloco que já verifica self.official)
- Adiciona TODO/docstring em _format_issn_type sinalizando possível
  inversão na lógica de negócio (issn_print == issn_scielo retorna
  'ONLIN', o que parece invertido)
- Corrige docstring typo 'Title Journalal' -> 'Title Journal'
- Remove comentários óbvios/redundantes (e.g. 'tem que ser objeto
  datetime', 'Deixa preparado para tornar obsoleto')
- Normaliza trailing whitespace e vírgulas finais em dicts/listas
…et e robustez

- Substitui @lru_cache por cache manual em medline_titles para evitar
  memory leak (mesmo padrão aplicado no journal formatter)
- Substitui atributo self.article (queryset avaliado no __init__) por
  property lazy self.article_qs que só executa a query quando acessado,
  evitando query desnecessária quando o dado não é utilizado
- Corrige _format_article_info: usa self.article_qs (filtrado por
  issue + journal) em vez de self.obj.article_set (sem filtro por
  journal), garantindo contagem consistente com o queryset da classe
- Adiciona guard clause em _format_field_use_system para checar
  journal_acron antes de chamar .upper(), e usa 'or empty string'
  para volume/number None, evitando concatenação com None
- Refatora _format_issn_info com early return (guard clause) para
  reduzir nível de indentação; adiciona placeholder v435 no
  add_multiple_to_result para documentar que será preenchido por
  _format_issn_with_type
- Adiciona guard 'if issns' antes de atribuir v435 em
  _format_issn_with_type para evitar lista vazia no resultado
- Melhora select_related em _format_code_sections: inclui
  'journal_toc' além de 'journal_toc__language'; adiciona guard
  'if not journal_toc: continue' para pular registros sem relação
- Remove comentários redundantes (e.g. 'Path to base issue',
  'Ordem de publicação', 'Só adiciona v/n se houver')
- Remove import não utilizado 'from functools import lru_cache'
- Corrige missing newline at end of file
@robertatakenaka robertatakenaka merged commit ba19711 into scieloorg:main Mar 26, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant