Prevalência x Concorrência

O que vcs acham disso ??? Num sistema prevalente, a sincronização tem que ser feita pelo programador, e isso será passível a muitos bugs e race-conditions. O grande pulo do gato do Oracle é realmente como o Marcos falou: “Only writers, block writers.” Num sistema prevalente, Readers can block Writers, e Writers can block Readers. É arriscado.

Use read-write locks ue!

Como? No Oracle isso é transparente, ou seja, o programador não precisa se preocupar (muito) com isso.

Num sistema prevalente se o cara pega um Iterator e faz um acesso serial, já aí ele pode ganhar um ConcurrentModificationException.

Claro que ele pode sincronizar a coisa, mas aí as chances de ele fazer caca são altas, e ele vai acabar fazendo um reader bloquear um writer o que pode fazer o sistema todo parar.

Faz sentido essa minha preocupação ??? Quem levantou a bola foi o Marcos e eu tive que concordar com ele.

Se o problema é o ConcurrentModificationException, programe uma collection que retorna iteradores que funcione mediante a modificações externas, garanto que vão ser bem mais lentos que os do java.util…

No Prevayler isso foi resolvido tendo Queries. Elas sao executadas de forma sincronizada com as Transactions, e portanto, “seguras” quanto à modificacao concorrente. Se vc nao precisa dessa seguranca, pode acessar de uma vez, sem criar Queries. Pros acessos mais sensiveis, Query nele :smiley:

No Space4J, o Space possui funções para retornar safe Iterators de suas collections. Isso é obtido com as função toArray() da Collection, que faz um System.arraycopy de todas as referências para um novo array isolado.

Isso é legal, mas se a Collection for muito grande, pode demorar um pouco. Ou talvez não, pois o System.arraycopy é bem rápido.

Pensando melhor, acho que não procede a afirmação de que num sistema prevalente “Readers will block Writers”, pois olhando mais de perto o código fonte das Collections, não existe nenhum método de leitura que vai bloquear um método de escrita. O máximo que vai acontecer é o Reader ganhar um ConcurrentModificationException se não estiver usando um safe Iterator. (Concordam?)

No Space4J, todas os acessos de leitura são executados assincronamente, ou seja, concorrentemente com as alterações. Existe alguma ocasião em que isso será perigoso ???

Sergio

Vale lembrar que ConcurrentModificationException são lançadas em regime de melhor-esforço e contar com esse comportamento é errado e não deve ser feito (isso ta na documentação do Iterator).

Oque customa dar problema é iteração apenas.
Para isso voce pode usar sincronização explicita (r/w locks ou blocos sincronizados) ou executar o código como uma transação sem side effects (uma query).

Existem dois únicos jeitos de lidar com concorrência: sincronização e falhas. Ou vc convive com a espera, ou com rollbacks.

Com prevalência dá pra usar Read/Write locks na boa, e aliás todo pacote util.concurrent (o famoso pacote do Doug Lea).

O Oracle tem uma vantagem em relação ao Prevayler, ele já tem tudo redundante (em disco e em RAM), então é fácil fazer write-on-release. No Prevayler, se vc quiser um comportamento parecido, c pode brincar de food taster e fazer as leituras lerem de uma cópia do sistema. Resultado, vc dobra a quantidade de memória que o seu sistema exige.

E essa história de “writers don’t block readers” é meio lenda, pq é também um requisito que um reader que puxe um dado recém atualizado depois que o writer deu commit tem que receber o dado novo. O Oracle pode otimizar tudo, mas em algum momento tem I/O aí e ele não faz nem leitura, nem escrita, nem nada… no prevayler, vc de vez em quando tira um snapshot do sistema “rw” e copia pro “ro”… e ganha de brinde o problema (interessante, é verdade) de descobrir quando atualizar a cópia read-only.

Fora que se sua query roda 2000 vezes mais rápido, seus 16 processadores vão estar bem mais ocupados fazendo coisas úteis que não requerem sincronização do que copiando blocos de um lado pro outro da RAM pra garantir mais threads.

Pessoal parece que não lembra que toda essa modernidade que a gente conhece é feita em cima daqueles velhos algoritmos do livrinho do Kernigham e Ritchie (perdão se eu errei o nome)…

[]s!!

[quote=“dukejeffrie”]
Pessoal parece que não lembra que toda essa modernidade que a gente conhece é feita em cima daqueles velhos algoritmos do livrinho do Kernigham e Ritchie (perdão se eu errei o nome)…
[]s!![/quote]
Vai me perdoar, mas o único livro que conheço deles é o ‘The C programming language’, qual seria este que voce está falando?

Um RBDMS pode implementar COW para introduzir paralelismo em transações serializadas. Isso não da para fazer no prevayler.

[quote=“louds”][quote=“dukejeffrie”]
Pessoal parece que não lembra que toda essa modernidade que a gente conhece é feita em cima daqueles velhos algoritmos do livrinho do Kernigham e Ritchie (perdão se eu errei o nome)…
[]s!![/quote]
Vai me perdoar, mas o único livro que conheço deles é o ‘The C programming language’, qual seria este que voce está falando?
[/quote]

Isso!! Esse mesmo! onde estão todos os algoritmos famosos, populares e impopulares do mundo da computação. OK, Ok, existem outrs algoritmos que não estão lá. Mas são pra coisas específicas…

o que é COW??

[]s

Fala Duke !!! Qual o seu nome verdadeiro ??? :slight_smile: Vc foi quem me deu a luz de usar uma SmartReference ao invés do Proxy na questão da passivação. Depois me diga o que vc achou do framework de passivação e se achar que eu esqueci alguma coisa por favor fale. :smiley:

Quanto a questão da concorrência, vc falou numa linguagem um pouco obscura. :lol: O pouco que eu entendi foi que vc está preocupado com a replicação das alterações feitas por um cliente nos outros clientes. Mais ou menos o que acontece quando vc dá um commit no DB, ou seja, todas as outras conexões passam a ver aquele registro atualizado instantaneamente.

Num sistema prevalente, ao invés de conexões temos referências aos objetos. Logo as alterações são instantaneamente refletidas para todos os clientes que possuem a referência para o objeto. Mas isso é óbvio, então acho que eu realmente não entendi nada. Qual é o problema em questão?

Um abraço,

Sergio

[quote=“saoj”]Fala Duke !!! Qual o seu nome verdadeiro ??? :slight_smile: Vc foi quem me deu a luz de usar uma SmartReference ao invés do Proxy na questão da passivação. Depois me diga o que vc achou do framework de passivação e se achar que eu esqueci alguma coisa por favor fale. :smiley:
[/quote]

Fala Sergio! Meu nome verdadeiro é Duke, mas meu id na Matrix é Tiago.

Bom, eu vou fazer um esforço pra explicar sem desenhar e sem usar as mãos.

Tudo começa quando vc tem duas Threads (vc tem várias, mas por um capricho do destino, essas duas vão brigar)…

Uma thread lê um dado. Quando eu digo lê um dado, significa que ela lê um campo em algum objeto que está no seu sistema prevalente. Vamos esquecer por um momento getters e setters, fields, etc. Só fique gravado que um snapshot imediatamente antes da leitura é identicamente igual a um imediatamente depois. Vamos chamar a Thread de leitor, acho que ela não se importa da gente mudar o sexo dela.

Daí vem concorrentemente um escritor. A outra Thread acessa o mesmo dado, mas ela lê e muda o valor dele.

Ótimo, eu te digo assim:

[quote]Eu tiro um snapshot A, executo um dos dois comandos, depois tiro um snapshot B, executo o outro comando, depos um snapshot C. Perguntas:
A == B?
B == C?
A == C?
[/quote]

Note que se eu não contar qual comando rodou primeiro, vc não consegue responder, né??

Até agora, só tô enrolando, só defini esses elementos que eu quero usar: leitor, escritor, A, B, C.

O problema pode ser resolvido na boa com sincronização… mas a gente quer fazer sem! Eu leio transações serializadas que vão chegando, e meu progz não bloqueia leitores, então pode acontecer assim:

  1. escritor lê
  2. leitor lê
  3. escritor escreve
  4. leitor comita
  5. escritor comita

Na boa, não aconteceu nada…

O que eu tava tentando dizer é que entre 3 e 5, se chegar um outro leitor, eu preciso decidir o que eu faço. Pq se eu sobrescrevi o valor, não posso mais dar o valor velho pro leitor novo, mas eu tb não posso deixar ele ler o valor novo, pq o escritor não deu commit.

Só tem uma saída: replicação. O escritor não grava nada direto no lugar onde o dado está, ele grava um “futuro valor novo”. Enquanto ele não comita, outros leitores podem continuar lendo o valor velho (outros escritores estão bloqueados).

Mas agora vamos abrir o item 5…

5.0. no-op (só pra eu falar lá embaixo)
5.1. fecha a porta para leitores
5.2. copia o valor futuro pro dado
5.3. abre a porta pra leitores, libera o lock de escrita.

Não tem jeito, se eu não fechar a porta pros leitores, rola o problema do A == B entre (logo depois do) 5.0 e (logo antes do)5.3.

Tem manhas, do tipo, eu sei que atribuição de referência é atômica, então eu posso transformar meu 5.2 numa operacão atômica. Mas requer um nível extra de indireção, que me obriga a trancar o acesso de qq jeito.

Bom, foi o melhor que eu consegui explicar, espero não ter falado nenhuma besteira, mas pode acontecer, viu??

[]s

Fala Duke,

Algumas dúvidas:

  1. Leitor não pode ser comando, pelo menos no Space4J. O leitor vai executar em paralelo com os comandos (transações).

  2. Leitor vai comitar o que se ele está apenas lendo ???

Pelo que entendi, vc está preocupado em lockar o objeto na hora que o escritor for alterá-lo. Mas acho que isso é desncessário, pelo seguinte:

  1. Se o objeto for imutável, o leitor que já tiver pegado uma referência pra ele vai ficar com uma cópia desatualizada. A vida é assim. Em todos os sistemas é assim.

  2. Se o objeto for mutável, o leitor vai instantaneamente ver as modificações do escritor.

[quote=“dukejeffrie”]Isso!! Esse mesmo! onde estão todos os algoritmos famosos, populares e impopulares do mundo da computação. OK, Ok, existem outrs algoritmos que não estão lá. Mas são pra coisas específicas…

o que é COW??

[]s[/quote]

Nesse caso sou muito mais o livro do Cormen, Leisenson e Rivest. COW é Copy-On-Write.

Isso junto com RCU (Read-Copy-Update) são técnicas para aumentar o throughtput de um sistema diminuindo a contenção. A ideia de usar COW seria isolar a copia de trabalho de uma transação do sistema inteiro, ou seja, toda modificação é feita em objetos locais a ela e depois, durante o commit, elas são colocadas novamente no sistema.
RCU é um método para voce modificar valores de forma lock-free, criando uma copia, atualizando ela e depois atribuindo para a referencia à original. Esse mecanismo funciona muito bem em java devido ao GC, ai bastaria que nenhum objeto mantivesse uma copia local do valor para funcionar.

[quote=“louds”]Nesse caso sou muito mais o livro do Cormen, Leisenson e Rivest. COW é Copy-On-Write.
[/quote]

Legal, vou meter os 3 nominhos no Google e ver o que sai…

[quote=“louds”]Isso junto com RCU (Read-Copy-Update) são técnicas para aumentar o throughtput de um sistema diminuindo a contenção. A ideia de usar COW seria isolar a copia de trabalho de uma transação do sistema inteiro, ou seja, toda modificação é feita em objetos locais a ela e depois, durante o commit, elas são colocadas novamente no sistema.
[/quote]

Eba, nada do que eu escrevi lá em cima é novidade!! : )

Aí, Sergio, onde eu disse “tem manhas”, leia RCU…

[]s!
p.s.: mas o nível extra de indireção não dá pau? Ah, ler valor velho tá beleza… hmmm… (tiago pensando)…

[quote=“dukejeffrie”]
[]s!
p.s.: mas o nível extra de indireção não dá pau? Ah, ler valor velho tá beleza… hmmm… (tiago pensando)…[/quote]

RCU para funcionar precisa de 2 coisas importantes:

-Somente uma referencia aos dados pode existir fora da pilha.
-Atribuição tem que ser atomica, a variavel tem que ser volatil; o no JMM do 1.5 resolve esse detalhe, apenas que todas VMs 1.4.x suportam essa semântica.

Não existe problema de ler o valor velho, pq suportamente vc vai fazer assim:

void bla(Foo f) {
 FooData fd = f.getData().
 ...
 ...
 ...
 fd = new FooData("foo", "data");
 f.setData(fd);
}

Há alguma forma de isolar as Transactions em discos diferentes dos Snapshots,para evitar uma eventual paralisia(read-only) se eu for gravar por exemplo um Snap de 2GB,numa aplicação em que as escritas não podem parar(ainda que poucas)???

Para fazer um Snapshot, vc não precisa parar o sistema.

O único problema é que vai formar uma fila grande de writes na máquina que esta fazendo o snapshot, se o snapshot demorar muito.

O ideal é que essa máquina que faz o snapshot seja uma máquina isolada, apenas para fazer isso.

É isso que vc perguntou ??? Cara, não mexo com o Space4J a meses…

Sérgio,era isso mesmo q tava querendo saber…se dava para isolar em máquinas distintas os snaps das transactions para evitar a fila de writes em um sistema q precisa ficar disponível para escrita 24horas.Mas no sistema tradicional(tudo numa mesma máquina),esse lock q segura as transactions antes do Snaps terminar de ser gravado é thread safe?(não corre risco de haver um estado incosistente entre a espera pora poder gravar essas transactions-imagine uns 10MB de transactions na fila… )
Desculpe essas perguntas simples pq eu mexo com prevalência normalmente com pouca quantidade de dados…(Só KB)preciso dessa info para apresentar meu pf…pelos meus humildes cálculos(manuais),num raptor(em raid0)os snaps de 2GB iriam em menos de 10min,o q não seria um tempo tão alto.

Te recomendo dar uma olhada no esquema de replicação do Space4J que está bem clean.

Durante um snapsthot, uma das máquinas tem que parar, obrigatoriamente, mas não o sistema inteiro.

Se vc estiver utilizando o Space4J sem replicação, isto é, numa máquina (virtual) apenas, o seu sistema vai ter que obrigatoriamente parar durante o Snapshot.

Quando vc tem um ambiente replicado, e vc pode se dar ao luxo de ter uma máquina especialmente para o snapshot, então a coisa flui bonito.