Skip to content

Arquitetura de Dados

Esta pagina documenta todas as decisoes arquiteturais, mecanismos de integridade, performance e robustez implementados na camada de dados da plataforma ProCases.


Visao Geral

ComponenteTecnologiaVersao
Banco PrincipalPostgreSQL15+ (Alpine)
ORMPrisma5.10.2
Cache / Locks / Pub/SubRedis7+
IDsSnowflake (customizado)64-bit BigInt
Valores MonetariosBIGINT centavosZero imprecisao

Decisoes Arquiteturais

Por que PostgreSQL Nativo (sem TimescaleDB)

A plataforma utiliza PostgreSQL 15 padrao com extensoes nativas ao inves de TimescaleDB. Motivos:

CriterioPostgreSQL NativoTimescaleDB
Complexidade operacionalBaixaMedia-Alta (extensao + versao compativel)
Compatibilidade PrismaTotalParcial (hypertables nao suportadas pelo Prisma)
ParticionamentoNativo (PARTITION BY RANGE)Automatico (hypertables)
Caso de uso idealTransacional + analitico mistoSeries temporais puras (IoT, metricas/s)
OverheadZero~5-10% em writes normais
Busca fuzzypg_trgm + GINpg_trgm + GIN (igual)
CustoZeroLicenca Enterprise para features avancadas

Conclusao: Para uma plataforma de gambling com carga transacional mista (financeiro + gaming + analytics), PostgreSQL nativo com partial indexes e particionamento manual oferece controle total sem dependencias extras.

Por que PostgreSQL Full-Text Search (sem Elasticsearch)

A extensao pg_trgm com index GIN resolve busca fuzzy por nome de item sem necessidade de um servico externo:

sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_items_name_trgm ON items USING gin (name gin_trgm_ops);
  • Busca fuzzy: WHERE name % 'dragon' encontra "Dragon Lore", "Dragonfire"
  • Busca parcial: WHERE name ILIKE '%drag%' com performance O(1) via GIN
  • Zero infraestrutura extra: Sem Elasticsearch, sem sincronizacao de dados
  • Consistencia garantida: Dados sempre atualizados (mesmo banco)

Por que BIGINT para Valores Monetarios (sem Decimal/Float)

Todos os modelos com campos monetarios utilizam BigInt. Nenhum campo usa Decimal, Float ou String para dinheiro.

R$ 1.863,21 = 186321 centavos (BigInt)

Campos monetarios implementados (todos BigInt):

TabelaCampos
UserbalanceCents, totalDepositedCents, totalWithdrawnCents, totalWageredCents
TransactionamountCents
DepositamountCents
WithdrawalamountCents
ItemvalueCents (USD), valueCentsBrl (BRL), originalPriceCents
CaseOpeningpricePaidCents, itemValueCents, profitCents
CasepriceCents
UpgradevalueInvestedCents, balanceUsedCents, targetValueCents
BattletotalValueCents
BattlePlayertotalProfitCents
BattleSettlementvalueCents
RafflePrizevalueCents, valueCentsBrl

Beneficios:

  • Zero erro de ponto flutuante (0.1 + 0.2 == 0.3 sempre)
  • Operacoes atomicas no banco (incremento/decremento sem race condition)
  • Comparacoes exatas (sem tolerancia de epsilon)
  • Compativel com JavaScript BigInt nativo

Por que Snowflake IDs (sem UUID / Auto-Increment)

Todos os IDs sao Snowflake de 64 bits gerados localmente:

[41 bits timestamp][10 bits machine ID][12 bits sequence]
ParametroValor
Epoch1 de Janeiro de 2024 (1704067200000)
Timestamp41 bits (~69 anos ate overflow)
Machine ID10 bits (1024 maquinas)
Sequence12 bits (4096 IDs/ms/maquina)
Tamanho8 bytes (BigInt)

Comparacao:

CriterioSnowflakeUUID v4Auto-Increment
Tamanho8 bytes16 bytes4-8 bytes
Ordenavel por tempoSimNaoSim
Sem coordenacaoSimSimNao (requer banco)
Seguranca (nao previsivel)MediaAltaBaixa
Indexacao B-treeOtimaRuim (aleatorio)Otima
Multi-instanciaSim (machine ID)SimNao

Protecoes implementadas:

  • Clock drift: recusa gerar se relogio voltar
  • Sequence overflow: aguarda proximo milissegundo se 4096 IDs esgotados
  • Machine ID: configuravel via MACHINE_ID (variavel de ambiente)
  • Batch: generateMany(count) para criacao em lote

Schema: 59 Modelos

Diagrama de Relacionamentos

Principais Modelos por Dominio

DominioModelosTotal
Usuarios e AuthUser, Session2
FinanceiroTransaction, Deposit, Withdrawal3
CaixasCase, CaseCategory, CaseItem, CaseOpening, Item, UserInventory6
BatalhasBattle, BattlePlayer, BattleCase, BattleRound, BattleSettlement5
UpgradesUpgrade, UpgradeItem2
SwapsSwap, SwapItem, SiteSwap, SiteSwapItem4
SorteiosRaffle, RafflePrize, RaffleTicket3
EventosEvent, EventLevel, EventProgress, EventReward, EventShootItem, EventShootOpening6
SaquesWaxpeerWithdrawal1
NotificacoesNotification1
AuditoriaAuditLog1
Subtotal (nucleo)34

O codigo atual possui 59 modelos (grep '^model ' prisma/schema.prisma). Alem do nucleo acima, os subsistemas mais recentes adicionam modelos proprios:

DominioModelosTotal
CrashCrashRound, CrashBet, CrashBetSkin, CrashCycle, CrashRoundProfitDecision, CrashSeedForge, CrashProfitPoolConfig7
MinesMinesGame, MinesConfig, MinesSeed, MinesBetSkin4
Deposito Cripto (CoinPayments)CryptoDeposit, CryptoDepositConfig2
Deposito Skins (SkinsBack)SkinsBackDeposit, SkinsBackDepositConfig2
Saque Automatico (Waxpeer)WithdrawalAutoApprovalConfig, UserWithdrawalLimits2

35 Enums Tipados

Enums no PostgreSQL previnem estados invalidos na camada de banco. Qualquer valor fora do enum causa erro no INSERT/UPDATE. A tabela lista os enums principais; o codigo atual define 35 enums (grep '^enum ' prisma/schema.prisma).

EnumValoresUso
UserStatusACTIVE, SUSPENDED, BANNEDEstado da conta
UserRoleUSER, ADMIN, SUPER_ADMINNivel de acesso
TransactionTypeDEBIT, CREDITDouble-entry
TransactionStatusPENDING, COMPLETED, FAILED, CANCELLEDEstado da transacao
PaymentMethodPIX, CREDIT_CARD, CRYPTOMetodo de deposito
DepositStatusPENDING, CONFIRMED, FAILED, EXPIREDEstado do deposito
WithdrawalMethodSTEAM_TRADE, PIXMetodo de saque
WithdrawalStatusPENDING_2FA, PROCESSING, COMPLETED, FAILED, CANCELLEDEstado do saque
CaseStatusACTIVE, INACTIVE, COMING_SOONDisponibilidade da caixa
RiskLevelLOW, MEDIUM, HIGH, MAXIMUMNivel de risco
RarityCONSUMER, INDUSTRIAL, MIL_SPEC, RESTRICTED, CLASSIFIED, COVERT, EXTRAORDINARYRaridade do item
WeaponCategoryPISTOL, RIFLE, SMG, SNIPER, SHOTGUN, HEAVY, KNIFE, GLOVE, OTHERTipo de arma
ItemExteriorFACTORY_NEW, MINIMAL_WEAR, FIELD_TESTED, WELL_WORN, BATTLE_SCARRED, NOT_PAINTEDCondicao
InventoryItemStatusAVAILABLE, LOCKED, SOLD, WITHDRAWN, USED_IN_UPGRADE, USED_IN_BATTLE, IN_SWAP, IN_CRASH, USED_IN_CRASH, IN_MINES, USED_IN_MINESEstado do item
WaxpeerWithdrawalStatusPENDING_APPROVAL, PENDING, WAITING_SELLER, TRADE_SENT, COMPLETED, CANCELLED, FAILEDTracking de saque
NotificationTypeDEPOSIT_CONFIRMED, WITHDRAWAL_COMPLETED, WITHDRAWAL_FAILED, CASE_OPENING, UPGRADE_SUCCESS, UPGRADE_FAIL, SYSTEM, PROMOTIONTipo de notificacao
NotificationStatusUNREAD, READ, ARCHIVEDEstado da notificacao
BattleModeNORMAL, FLIP, SHAREDModo da batalha
BattleTypeONE_VS_ONE, TWO_VS_TWO, THREE_VS_THREE, FOUR_VS_FOUR, ONE_VS_ONE_VS_ONE, ONE_VS_ONE_VS_ONE_VS_ONEFormato
BattleStatusWAITING, IN_PROGRESS, FINISHED, CANCELLEDEstado da batalha
BattleSettlementTypeDROPPED, REMOVED_TO_POOL, ADDED_FROM_POOLTipo de distribuicao
SwapStatusPENDING, ACCEPTED, DECLINED, CANCELLED, EXPIREDEstado da troca
RaffleStatusPENDING, ACTIVE, DRAWING, COMPLETED, CANCELLEDEstado do sorteio
EventTypeBATTLE_PASS, SEASONAL_EVENT, SPECIAL_EVENTTipo de evento
EventStatusUPCOMING, ACTIVE, ENDEDEstado do evento
RewardTypeITEM, BALANCE, TICKETS, BADGETipo de recompensa
SkinsBackDepositStatusPENDING, WAITING, IN_HOLD, HOLD_APPROVED, COMPLETED, FAILED, RETURNEDDeposito de skins (SkinsBack)
CryptoDepositStatusPENDING, AWAITING_PAYMENT, PARTIAL, CONFIRMING, PAID, COMPLETED, CANCELLED, EXPIRED, FAILEDDeposito cripto (CoinPayments)
CrashCycleStatusACTIVE, COMPLETED, ABORTEDCiclo de profit-control do Crash
CrashProfitModeDISABLED, FREE_BOTS_ONLY, FREE_PLAYERS, FORCE_CRASH_CYCLE, FORCE_CRASH_POOL_DEPLETED, FORCE_CRASH_BUDGET_GUARDModo de decisao por rodada do Crash

17 Unique Constraints

Constraints de unicidade garantem que dados criticos nunca sejam duplicados, independente de logica de aplicacao.

Campo Unico

TabelaCampoProtege contra
UsersteamIdConta duplicada
UserreferralCodeCodigo de indicacao duplicado
TransactionrelatedTransactionIdDouble-entry orfao
DepositexternalIdDeposito processado 2x
CaseCategorynameCategoria duplicada
CaseCategoryslugSlug duplicado
CaseslugURL duplicada
ItemmarketHashNameItem duplicado no catalogo
WaxpeerWithdrawalwaxpeerIdTracking duplicado
WaxpeerWithdrawalprojectIdProjeto duplicado
BattlejoinCodeCodigo de entrada duplicado

Constraint Composta

TabelaCamposProtege contra
CaseItem[caseId, itemId]Mesmo item 2x na caixa
BattlePlayer[battleId, userId]Jogador entrando 2x
RaffleTicket[raffleId, ticketNumber]Numero de ticket duplicado
EventLevel[eventId, level]Nivel duplicado no evento
EventProgress[eventId, userId]Progresso duplicado
EventReward[eventLevelId, userId]Recompensa recebida 2x

17 Cascade Deletes

Quando uma entidade pai e deletada, todas as entidades filhas sao removidas automaticamente pelo PostgreSQL, garantindo integridade referencial sem orfaos.

Entidade PaiEntidade FilhaQuando Pai e Deletado
UserSessionTodas as sessoes removidas
UserNotificationNotificacoes removidas
UserEventProgressProgresso removido
UserEventRewardRecompensas removidas
CaseCaseItemItems da caixa removidos
UpgradeUpgradeItemItems do upgrade removidos
BattleBattlePlayerJogadores removidos
BattleBattleCaseCases da batalha removidas
BattleBattleRoundRounds removidos
BattleBattleSettlementSettlements removidos
SwapSwapItemItems da troca removidos
SiteSwapSiteSwapItemItems da troca removidos
RaffleRafflePrizePremios removidos
RaffleRaffleTicketTickets removidos
EventEventLevelNiveis removidos
EventEventProgressProgresso removido
EventLevelEventRewardRecompensas removidas

Integridade Financeira

Double-Entry Bookkeeping

Toda operacao financeira gera dois registros atomicos na tabela Transaction: um DEBIT e um CREDIT pareados via relatedTransactionId.

Abertura de Caixa (R$ 15,90):
  DEBIT  → userId: 123, amountCents: 1590, reason: "Case opening #456"
  CREDIT → userId: 0 (SYSTEM), amountCents: 1590, relatedTransactionId: <debit_id>

Invariante: SUM(CREDITS) - SUM(DEBITS) = 0 (sistema inteiro)

Metodos do TransactionService:

MetodoDeParaUso
debit(userId, amount)UsuarioSistema (ID 0)Abertura, upgrade, batalha
credit(userId, amount)Sistema (ID 0)UsuarioDeposito, venda de item
transfer(from, to, amount)UsuarioUsuarioTransferencia P2P
createDoubleEntry(debit, credit, amount)CustomizavelCustomizavelOperacoes especiais

Source of Truth do Saldo

O saldo real e sempre calculado por SUM(CREDITS) - SUM(DEBITS) na tabela Transaction. O campo user.balanceCents e apenas um cache atualizado apos cada transacao.

Source of Truth: TransactionRepository.getUserBalance(userId) → GROUP BY
Cache:          User.balanceCents (atualizado apos cada TX, NAO confiavel para operacoes)

Distributed Locks (Redlock)

Locks distribuidos via Redis previnem race conditions em operacoes financeiras. Padrao: Lock → Prisma $transaction → Double-Entry → Commit → Release.

LockTTLRetriesUso
user:balance:{userId}10s10x (200ms + 200ms jitter)Toda operacao de saldo
transaction:create5s10xCriacao de double-entry
withdrawal:{inventoryItemId}30s10xSaque de item

Configuracao Redlock:

  • Drift factor: 0.01 (1% tolerancia de clock)
  • Auto-extension: threshold de 500ms (estende lock se < 500ms restantes)

Audit Log com Hash Chaining

Cada registro de auditoria inclui o hash SHA-256 do registro anterior, formando uma cadeia imutavel verificavel. Alteracao em qualquer registro intermediario quebra a cadeia.

Log #1: currentHash = SHA256("data1")
Log #2: currentHash = SHA256("hash1:data2")
Log #3: currentHash = SHA256("hash2:data3")
...

Verificacao: GET /api/audit/verify-chain percorre a cadeia e valida cada hash. Se qualquer registro foi alterado, retorna o ID do registro corrompido.

Transacoes Prisma

Todas as operacoes financeiras rodam dentro de prisma.$transaction():

ParametroValor
maxWait10 segundos (tempo maximo na fila de conexoes)
timeout30 segundos (tempo maximo de execucao da transacao)
IsolationRead Committed (padrao PostgreSQL)

Padrao no codigo: Use cases aceitam externalTx?: PrismaTransactionClient para composicao de transacoes, permitindo que multiplas operacoes rodem na mesma transacao atomica.


Performance: 49+ Indexes

A migration 20260202120000_add_performance_indexes cria 49 indexes adicionais aos indexes do Prisma schema, incluindo 1 extensao PostgreSQL.

Extensao PostgreSQL

sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;

Habilita busca por similaridade de trigramas, permitindo queries fuzzy com performance de index invertido.

Distribuicao por Tipo

TipoQuantidadeBeneficio
Composite (multi-coluna)28Cobrem queries com multiplos filtros
Partial (com WHERE)16Indexam apenas registros relevantes, tamanho reduzido
Standard B-tree4Queries simples de campo unico
GIN (trigram)1Busca fuzzy/parcial por nome

Indexes Criticos por Sistema

Provably Fair (Abertura de Caixas)

sql
CREATE INDEX idx_case_items_case_hash_range
ON case_items (case_id, hash_range_start, hash_range_end);

Lookup O(1) para encontrar item baseado no roll. Sem este index, cada abertura faria scan de todos os items da caixa.

Sistema FLIP

sql
CREATE INDEX idx_case_items_rare
ON case_items (case_id, is_rare) WHERE is_rare = true;

Partial index sobre itens do tier raro (is_rare). O FLIP real e threshold por caixa (roll >= case.flipThreshold, com roll unico — sem segundo sorteio independente); o index acelera a montagem da roleta com os itens do tier raro (topo dos hash ranges).

Calculo de Saldo

sql
CREATE INDEX idx_transactions_user_type_status
ON transactions (user_id, type, status);

Cobre a query de getUserBalance() que faz GROUP BY type WHERE userId = X AND status = 'COMPLETED'.

Inventario do Usuario

sql
CREATE INDEX idx_inventory_available
ON user_inventory (user_id, status, item_id) WHERE status = 'AVAILABLE';

Partial index: indexa apenas items AVAILABLE. Como a maioria dos items eventualmente sai do inventario (vendido, sacado, usado), este index permanece compacto mesmo com milhoes de registros totais.

Lobby de Batalhas

sql
CREATE INDEX idx_battles_public_waiting
ON battles (status, is_private, created_at DESC)
WHERE status = 'WAITING' AND is_private = false;

Partial index para o lobby: indexa apenas batalhas publicas esperando jogadores. A maioria das batalhas esta FINISHED, entao este index cobre <1% da tabela.

Live Drops

sql
CREATE INDEX idx_case_openings_recent ON case_openings (created_at DESC);
CREATE INDEX idx_case_openings_item ON case_openings (item_id, created_at DESC);

Queries de live drops (aberturas recentes) sao servidas diretamente pelo index sem acessar a tabela (index-only scan).

Busca Fuzzy de Items

sql
CREATE INDEX idx_items_name_trgm ON items USING gin (name gin_trgm_ops);

GIN index com trigramas. Permite busca como WHERE name % 'dragon' ou WHERE name ILIKE '%drago%' com performance sub-milissegundo.

Todos os 49 Indexes

#IndexTabelaTipo
1idx_users_roleusersB-tree
2idx_users_referred_byusersPartial
3idx_users_total_wageredusersB-tree DESC
4idx_users_balanceusersB-tree DESC
5idx_transactions_reasontransactionsB-tree
6idx_transactions_user_type_statustransactionsComposite
7idx_transactions_related_usertransactionsPartial
8idx_items_name_trgmitemsGIN (pg_trgm)
9idx_items_market_hashitemsB-tree
10idx_items_rarity_valueitemsComposite
11idx_items_weapon_rarityitemsComposite
12idx_items_collectionitemsPartial
13idx_items_stattrakitemsPartial
14idx_items_value_brlitemsB-tree
15idx_case_items_case_hash_rangecase_itemsComposite
16idx_case_items_rarecase_itemsPartial
17idx_cases_public_listcasesComposite Partial
18idx_cases_slugcasesB-tree
19idx_case_openings_recentcase_openingsB-tree DESC
20idx_case_openings_itemcase_openingsComposite
21idx_case_openings_profitcase_openingsB-tree
22idx_inventory_availableuser_inventoryComposite Partial
23idx_inventory_lockeduser_inventoryPartial
24idx_inventory_acquired_fromuser_inventoryPartial
25idx_battles_public_waitingbattlesComposite Partial
26idx_battles_modebattlesComposite
27idx_battles_typebattlesComposite
28idx_battles_winnerbattlesPartial
29idx_battle_players_teambattle_playersComposite
30idx_battle_rounds_itembattle_roundsB-tree
31idx_battle_rounds_flipbattle_roundsComposite Partial
32idx_battle_settlements_user_typebattle_settlementsComposite
33idx_upgrades_recentupgradesB-tree DESC
34idx_upgrades_success_userupgradesComposite
35idx_upgrades_targetupgradesB-tree
36idx_swaps_public_activeswapsComposite Partial
37idx_raffles_activerafflesComposite Partial
38idx_raffles_prizerafflesPartial
39idx_raffle_tickets_user_raffleraffle_ticketsComposite
40idx_deposits_pendingdepositsComposite Partial
41idx_deposits_methoddepositsComposite
42idx_withdrawals_pendingwithdrawalsComposite Partial
43idx_withdrawals_methodwithdrawalsComposite
44idx_notifications_unread_typenotificationsComposite Partial
45idx_events_activeeventsComposite Partial
46idx_event_progress_rankingevent_progressComposite
47idx_audit_entityaudit_logsComposite
48idx_audit_action_dateaudit_logsComposite
49idx_sessions_devicesessionsPartial

Redis: Cache, Locks e Pub/Sub

Redis serve como camada complementar ao PostgreSQL para operacoes que exigem latencia sub-milissegundo.

Funcoes do Redis

FuncaoUsoTTL
SessionsAutenticacao com binding de IP/UA7 dias de inatividade
Distributed LocksRedlock para operacoes financeiras5-30s por lock
Cache WaxpeerSnapshot de items do marketplaceConfiguravel
Pub/SubRelay de eventos WebSocket do Crash entre containers (canais crash:ws:emit:*)N/A
Exchange RatesTaxa USD/BRL em cacheConfiguravel

Separacao de Concerns

Redis DB 0: Sessions + Locks + Cache (padrao)
PostgreSQL: Source of Truth (financeiro, items, auditoria)

Regra: Redis e efemero. Se Redis reiniciar, o sistema perde sessions (usuarios precisam relogar) e locks (operacoes em andamento falham de forma segura), mas ZERO dados financeiros sao perdidos.


Migrations

O schema evolui via Prisma Migrate. Cada migration e um arquivo SQL versionado e imutavel.

Historico de Migrations (14)

#DataMigrationDescricao
12025-11-29initSchema inicial completo
22025-12-02add_bg_color_to_casesCor de fundo das caixas
32025-12-03add_flip_provably_fairCampos provably fair do FLIP
42025-12-03add_is_rare_to_case_itemFlag is_rare para sistema FLIP
52025-12-04add_battle_settlementSistema de distribuicao justa
62025-12-04add_battle_round_replay_fieldsDados de replay de batalha
72025-12-04add_balance_used_cents_to_upgradeRastreio de saldo em upgrades
82025-12-05remove_autoincrement_use_snowflakeMigracao para Snowflake IDs
92025-12-05add_acquired_fromRastreio de origem do item
102025-12-06add_site_swaps_tableSistema de swaps do site
112026-01-30add_value_cents_brlPrecos em BRL (Real)
122026-02-02add_performance_indexes49 indexes + pg_trgm
132026-02-02add_original_price_marginPreco original e margem
142026-02-11add_per_item_raffle_winnersVencedores por item no sorteio

Resumo de Robustez

MecanismoImplementacaoCobertura
Double-Entry BookkeepingTransactionService100% operacoes financeiras
Distributed LocksRedlock (10 retries, auto-extend)Saldo, transacoes, saques
BIGINT centavosPrisma schema100% campos monetarios
Audit Log imutavelHash chaining SHA-256Todas as operacoes auditaveis
Snowflake IDs64-bit (41+10+12 bits)100% das tabelas
Prisma TransactionsmaxWait 10s, timeout 30sTodas as operacoes criticas
pg_trgm + GINExtensao PostgreSQLBusca fuzzy de items
49+ Indexes16 Partial, 28 Composite, 1 GINTodas as queries criticas
35 Enums tipadosPostgreSQL native enumsPrevencao de estados invalidos
17 Unique ConstraintsCampo e compostaPrevencao de duplicatas
17 Cascade DeletesForeign keysIntegridade referencial
59 ModelosPrisma schemaDominio completo
14 MigrationsVersionamento SQLEvolucao controlada
Sessions Redis + IP/UABinding por sessaoDeteccao de hijacking
Validacao de entradaValidationPipe + SanitizePipeTodos os endpoints
31 Repositories (infra) + 30 interfaces (domain)Clean ArchitectureSeparacao dominio/infra

Documentação Técnica - ProCases