Análise da carga do servidor

Versão do documento 2026-05-29 · Contexto: 1300 ble_token_renew de um só convidado durante 4 horas com o proprietário offline, o que impulsionou a procura de outras fontes de tráfego HTTP e WebSocket a mais.

Já corrigido em 6.61–6.63

n.º 1 — repetições HTTP sem recuo FIX

Files: HostService.kt:1424-1432, Vault.kt:213

private val pendingKeysRunnable = object : Runnable {
    override fun run() {
        if (Prefs.isHostRegistered && Prefs.getPendingHostKeys().isNotEmpty()) {
            Thread { syncPendingHostKeys() }.start()
        }
        pendingKeysHandler.postDelayed(this, 60_000L)
    }
}

syncPendingHostKeys pega em TODAS as chaves de anfitrião pendentes e chama Api.keyCreatepara cada uma. Se a chamada HTTP falhar, a chave fica pendente e 60 s depois o ciclo repete-se.

O cenário descontrolado

Correção: recuo exponencial por chave

В Prefs guardar por localId:

Em cada passagem de syncPendingHostKeys:

val waitMs = (30_000L * (1L shl attemptCount)).coerceAtMost(30 * 60_000L)
if (now - attemptAt < waitMs) return@forEach  // skip
// otherwise try
val ok = try { Api.keyCreate(...); true } catch (e) { false }
if (ok) { clear attempt_at/count } else { attempt_count++; attempt_at = now }

Progression: 30 s → 1 min → 2 min → 4 min → 8 min → 16 min → 30 min (limite). Com uma falha permanente isso é um pedido a cada 30 minutos em vez de um por minuto. Uma redução de trinta vezes.

O mesmo vale para Vault.retryPendingBundles() — o mesmo padrão.

n.º 2 — as capturas do vídeo NÃO são carga nossa SKIP

snap_url é o endereço externo de uma câmara, gravador ou fonte HTTP. O cliente vai a esse URL diretamente pelo Coil ou ExoPlayer; o nosso servidor apenas passa o data_cipher cifrado que contém o endereço dentro do pacote. Não passa por nós nenhum fotograma.

Se um proprietário puser snap_int=1 para 10 convidados, o gravador dele recebe 10 pedidos por segundo. Essa é carga do gravador dele, não do nosso servidor.

Opcional: limitar por baixo o cursor da interface a três segundos. É questão de interface, não de infraestrutura.

n.º 3 — o agendador ble_renew SKIP

Já está travado pelas correções da 6.61:

Com dez objetos BLE, um utilizador envia dez mensagens WebSocket seguidas e o servidor executa dez ramos case 'ble_token_renew' seguidos. Uma ninharia.

n.º 4 — incrementar overridesVersion FIX

File: GuestConn.kt — 7+ locais.

AppState.overridesVersion = MutableState<Int>, um sinal do Compose com o sentido os dados mudaram, relê remember. É usado em ActionCard:

val ovrVersion = AppState.overridesVersion.value
val (overrideLabel, avatarPath) = remember(action.key, ovrVersion) {
    Prefs.getOverride(action.key)   // SharedPreferences IO
}
val snapCfg = remember(action.key, ovrVersion) {
    if (action.source == ActionSource.HOST_OWN) Prefs.getSnapshot(action.numberId)
    else Prefs.getIncomingSnapshot(...)
}
val tpl = remember(action.key, ovrVersion) { loadTpl() }  // more IO

Cada incremento faz cada cartão reler a substituição, o avatar, a captura e o modelo de SharedPreferences. Um utilizador com 20 cartões significa 80 leituras de SharedPreferences por incremento.

Onde estão os incrementos

LineContextÉ preciso?
L193após reingerir um pacote (sem K_obj)yes
L355após parseNumbers em numbers_updateredundant quando os números não mudaram
L411depois de carregar um avataryes
L558, L595após parseNumbers nos restantes tratadoresredundant quando os dados são idênticos

Onde isto carrega o servidor de forma indireta

Com um WebSocket instável, cada guest_ok → o tratador volta a analisar os números → incrementa overridesVersion → 80 leituras de SharedPreferences × 20 cartões × a frequência de guest_ok.

Isso é CPU local e E/S, não carga direta do servidor, mas:

Correção: comparar antes de incrementar

Antes de incrementar, comparar a nova lista JSON de numbers com a anterior, por hash ou comparando a lista de GuestNumberInfo). Idênticos → não incrementar.

private var lastNumbersHash: Int = 0

private fun maybeUpdateNumbers(arr: JSONArray?) {
    val parsed = parseNumbers(arr)
    val newHash = parsed.hashCode()  // or a hash of the JSON serialisation
    if (newHash == lastNumbersHash) return  // nothing changed
    lastNumbersHash = newHash
    numbers = parsed
    publish()
    AppState.overridesVersion.value = AppState.overridesVersion.value + 1
}

numbers_update chega com regularidade mas muda raramente — um proprietário não edita objetos a cada minuto. A otimização remove cerca de 90 % das ondas de redesenho supérfluas.

Conclusão: o que corrigir

n.º 1 (repetições HTTP sem recuo) — carga HTTP direta. Um simples recuo por chave corta 97 % dos pedidos em redes problemáticas.
n.º 4 (incrementar overridesVersion) — indireta, pelo gasto de bateria e CPU. Comparar antes de incrementar elimina 90 % das ondas de recomposição.
n.º 2 e n.º 3 saem da agenda: o vídeo vem de um gravador ou CDN externos, não por nós, e o agendador de renovações já está travado pelas correções da 6.61.