[PHP/SQL] usleep() tegen replicatie lag?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 22-10 10:42
2 snelle webservers en onze Ajax implementatie zorgen voor een klein probleempje. Onze MySQL master-slave replicatie is niet zo snel als dat de webservers de data serveren. Namelijk; POST naar webserver, schrijft gegevens naar Master-db. Daarna volgt een reload en worden de gegevens opnieuw opgehaald. Echter is de data dan nog niet geupdate op de Slave, dus krijg je oude informatie.

Nu was het idee dat een usleep(50) voor het ophalen van de gegevens het probleem op zou lossen. Dat is ook zo, alleen vraag ik mij af wat je met 500 concurent users voor situatie krijgt op je webservers... Is er een andere manier om dit op te lossen?

Config:
Server 1: Master server (MySQL 5.1.46), tabellen InnoDB
Server 2: Webserver (PHP 5.3.2) & Slave (MySQL 5.1.46) tabellen MEMORY & MyISAM
Server 3: Webserver (PHP 5.3.2) & Slave (MySQL 5.1.46) tabellen MEMORY & MyISAM

De webservers doen hun selects via de lokale socket (niet TCP_IP). Replication-mode staat op MIXED, dus statement based & row based replication.

Heeft het zin om de slaves ook naar InnoDB te converteren? MyISAM is in enkele gevallen sneller met SELECTs & COUNTs dus vandaar de keuze.

Acties:
  • 0 Henk 'm!

  • Radiant
  • Registratie: Juli 2003
  • Niet online

Radiant

Certified MS Bob Administrator

Ik vind het wel bizar dat je het met 50us wachten oplost, een nieuwe verbinding maken vanaf de browser duurt veel langer (maar dit is wellicht net op het randje).

De replication van MySQL is gewoon nogal bagger.
Misschien kan je iets met deze patch van google: http://code.google.com/p/.../wiki/SemiSyncReplication
Wel alleen voor MySQL 4.0/5.0 en alleen InnoDB. Wat het doet (ik gebruik het niet, alleen even gelezen) is een transaction commit laten blocken totdat de gehele transactie is gerepliceerd naar de slave.

Acties:
  • 0 Henk 'm!

  • mocean
  • Registratie: November 2000
  • Laatst online: 04-09 10:34
Als ik je goed begrijp, zou je met je loadbalancer kunnen zorgen dat sessies op één machine blijven, dat kan om meerdere redenen handig zijn. In dit geval zou het ook je probleem oplossen.

Koop of verkoop je webshop: ecquisition.com


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 07:51
(Semi-)synchronous replication is inderdaad wat je zoekt. Wachten met requests slaat nergens op. Je wil gewoon dat als je een transactie op de master gecommit hebt, je bevestiging hebt dat die data ook op de slaves gecommit is. Dat is standaard niet zo, maar dat kun je dus krijgen door semi-synchronous replication te configuren op de client en slaves.

Dit gaat wel enigzins ten koste van de snelheid van je updates, maar waarschijnlijk is dat geen groot probleem.

Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
En een andere oplossingsrichting: Vul caches (bv memcached) tijdens het wijzigen, zodat je niet meteen een cache miss hebt.

{signature}


Acties:
  • 0 Henk 'm!

  • Wolfboy
  • Registratie: Januari 2001
  • Niet online

Wolfboy

ubi dubium ibi libertas

Radiant schreef op maandag 14 juni 2010 @ 20:20:
Ik vind het wel bizar dat je het met 50us wachten oplost, een nieuwe verbinding maken vanaf de browser duurt veel langer (maar dit is wellicht net op het randje).
Je maakt de aanname dat PHP ook daadwerkelijk 50us wacht. Reken er maar op dat het veel langer is dan dat.

Blog [Stackoverflow] [LinkedIn]


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 22-10 10:42
@ Radiant: Dat is inderdaad ook bizar, het was in eerste instantie ook meer om te kijken of de snelheid het probleem was en dat is zo. Je linkje is inderdaad alleen MySQL 4.0x & 5.0x, eigenlijk wil ik niet terug naar 5.0x

@ Mocean: Nee dat maakt niet uit. De SELECTS worden lokaal uitgevoerd en de WRITES op een andere. De mysql-slaves fungeren ook als webserver maar de mysql-master niet.

@ Soultaker: Klopt helemaal, ik lees net het artikel op MySQL.com en dit moet inderdaad prima werken. Helaas word dit alleen ondersteund vanaf versie 5.5 en dat vind ik voor een productie omgeving misschien nog niet zo handig. Maar ik ga het wel even bekijken, wie weet!

@ Voutloos: Oké ik weet niet hoe dat werkt, maar ik ga even googlen. Heb je het nou hier over de slaves of over de master?

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Wolfboy schreef op dinsdag 15 juni 2010 @ 01:54:
[...]
Je maakt de aanname dat PHP ook daadwerkelijk 50us wacht. Reken er maar op dat het veel langer is dan dat.
Sowieso is die 50 een arbitrair gekozen waarde; het gaat misschien nu goed maar als straks de belasting wat hoger wordt ofzo zit je weer met exact hetzelfde probleem. Dat is ook precies de reden waarom dit soort "oplossingen" geen oplossing zijn maar een "cross your fingers and pray it works"-workaround.

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 22-10 10:42
RobIII schreef op dinsdag 15 juni 2010 @ 10:00:
Sowieso is die 50 een arbitrair gekozen waarde; het gaat misschien nu goed maar als straks de belasting wat hoger wordt ofzo zit je weer met exact hetzelfde probleem. Dat is ook precies de reden waarom dit soort "oplossingen" geen oplossing zijn maar een "cross your fingers and pray it works"-workaround.
Klopt, dit was meer om te kijken of het probleem echt aan replicatie-lag te wijten was. In MySQL 5.5 hebben ze middels de semi-sync replication al wel nagedacht over deze problemen. Ik ben er alleen niet zo'n fan van om deze versie al in een productie omgeving te gaan gebruiken.

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Bernardo schreef op dinsdag 15 juni 2010 @ 10:39:
[...]


Klopt, dit was meer om te kijken of het probleem echt aan replicatie-lag te wijten was. In MySQL 5.5 hebben ze middels de semi-sync replication al wel nagedacht over deze problemen. Ik ben er alleen niet zo'n fan van om deze versie al in een productie omgeving te gaan gebruiken.
Dat zou ik nog altijd gebruiken i.p.v. een sleep ;) Ik begrijp wel dat je dat gebruikte om even snel uit te zoeken waar 't probleem in zat; nu nog een (goede) oplossing kiezen ;) (Zie Voutloos / Soultaker)

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • eamelink
  • Registratie: Juni 2001
  • Niet online

eamelink

Droptikkels

Een andere mogelijke oplossing is dat je per client bijhoudt wanneer de laatste write is geweest. Als je dan ook bijhoudt wat de replication delay is (kan je opvragen bij je slaves), dan kan je dus bepalen welke servers je kan gebruiken om data op te halen.

Als bijvoorbeeld client A een write doet, en een hele andere client C doet een read, dan is het niet erg dat C data krijgt die een paar seconden oud is. Je kan dan voor C een slave pakken.

Maar als A een read doet, dan wil je dat niet, want dan krijgt hij stale data. Dan moet de read dus van de master komen. Als A echter een read doet na vijf seconden, kan hij weer via een slave.

Of het acceptabel is dat C data van een paar seconden oud krijgt is wel afhankelijk van je type applicatie natuurlijk :)

[ Voor 8% gewijzigd door eamelink op 15-06-2010 10:50 ]


Acties:
  • 0 Henk 'm!

  • creator1988
  • Registratie: Januari 2007
  • Laatst online: 22-10 21:47
Maakt het uberhaupt uit dat de data al in de database staat? Zeker als je gaat schalen naar meerder db-servers wil je niet constant hoeven wachten. Wij gebruiken de volgende tactiek:

Alle wijzigingen gaan naar de queue en xml-serialized in een temporary storage database.
Er loopt een service die de berichten uit de queue verwerkt en wegschrijft naar het mastercluster (maar dit zou meteen naar je frontend databases kunnen, als je geen transformatieslag nodig hebt).

Afhankelijk van welke data benodigd is:
Mocht er een realtime read nodig zijn (bijna altijd in de backoffice), dan halen we deze uit de temporary storage *mits* de data in de tempstorage nieuwer is dan de data in de frontend database. (dit abstraheer je gewoon weg in je datalayer).
Wat de bezoekers van onze website zien is de data uit de frontend database.

Dit schaalt makkelijk als je backoffice kleiner is dan je frontend, aangezien je writes nooit je reads gaan beïnvloeden.

Mocht je geen losse backoffice hebben, maar je frontend wijzigingen meteen sitewide nodig hebben, dan zou ik alsnog kiezen voor async wegschrijven van de wijzigingen naar een queue en een kortdurende cache gebruiken in een distributed caching system zoals memcached. Bij elke transactie de actie wegschrijven naar de queue, en meteen het cacheitem bijwerken. Na expiration komt de verse data uit de database.

Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 22-10 10:42
De memcached oplossing ziet er intressant uit, maar kan ik hier niet gebruiken. Elke user heeft (ongeveer) zijn eigen data, als ik voor elke update die gegevens naar memcached moet pushen word dat naar mijn idee teveel data. Behalve dat moet ik in mijn applicatie ontzettend veel aanpassen.

Nog even een korte toelichting op het type applicatie:

Het gaat om een spel, mensen kunnen paarden trainen en dat kan 1x per 3 minuten. Dus na het klikken op "trainen" (werkt iets anders, maar in feit zo), stuurt deze via jQUERY een post naar een script dat vervolgens dit allemaal afhandeld (gewoon page loader). Deze werkt de rijen bij in de database en laad vervolgens de pagina weer. Haald de paarden op en disabled te paarden die binnen de afgelopen 3 minuten al getrained zijn. Tussen het updaten (naar MySQL-master) en weergeven (van MySQL-slave) van die rijen is MySQL-master -> MySQL-slave blijkbaar nog niet gedaan.

@ eamelink: replication-delay is voor zover ik weet alleen in seconden en geen micro-/miliseconden. Een delay van enkele seconden is geen optie.

@ creator1988: De oplossing die je aankaart is inderdaad zeer chique, dat zeker. Dit is echter voor ons misschien te hoog gegrepen. We hebben geen apparte back-office, het is allemaal dun gelaagd en straight to the point. Loadbalancer -> Webserver -> db. De applicatie leest lokaal en schrijft naar een master. Maar jou idee is dus ook wegschrijven naar cache, data voor (bijv.) seconden gebruiken en dan lokaal lezen vanaf een up-to-date database. Is er in tegenstelling tot master-slave replication geen lag bij het gebruik van memcached? Het gaat hier al om slechts enkele microseconden.

In tegenstelling tot semisynchroon, is volledig synchroon misschien wel beschikbaar voor MySQL 5.1x ipv alleen 5.5, met een beetje geluk. Dat ga ik eens bekijken.

Mogelijke oplossingen:

* Upgraden naar MySQL 5.5 voor semisync
* Applicatie deels herschrijven en memcached gebruiken
* READ+WRITE scripts toestaan naar de master en READ scripts alleen op de slaves

Acties:
  • 0 Henk 'm!

  • eamelink
  • Registratie: Juni 2001
  • Niet online

eamelink

Droptikkels

Bernardo schreef op dinsdag 15 juni 2010 @ 12:02:
@ eamelink: replication-delay is voor zover ik weet alleen in seconden en geen micro-/miliseconden. Een delay van enkele seconden is geen optie.
Dat betekent dus alleen dat je de reads die binnen een seconde na een write gedaan worden op de master moet uitvoeren. Dat hoeft toch geen probleem te zijn? :)

Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Bernardo schreef op dinsdag 15 juni 2010 @ 12:02:
Is er in tegenstelling tot master-slave replication geen lag bij het gebruik van memcached?
Er is geen replication lag. Memcached in 't kort: alle nodes waar je een beetje geheugen over hebt laat je samenwerken als 1 cache. Een cache entry komt direct terecht op 1 node en alle clients weten op welke node ze moeten zijn.

Los van het huidige topic: memcached (en consorten) is bruut veel sneller dan zelfs de meest optimale db query. Als bepaalde data goed cachebaar is, zou het zomaar de grootste performance verbetering voor je app kunnen zijn.

Opzetten en gebruiken van memcached is ubersimpel (veel meer dan mijn korte verhaaltje doet memcached eigenlijk niet eens), maar je krijgt natuurlijk wel een nieuw, potentieel zeer complex, probleem bij: cache invalidatie.

Tot zover de intro, meer leesvoer op het internet. ;)

{signature}


Acties:
  • 0 Henk 'm!

  • creator1988
  • Registratie: Januari 2007
  • Laatst online: 22-10 21:47
Bernardo schreef op dinsdag 15 juni 2010 @ 12:02:
De memcached oplossing ziet er intressant uit, maar kan ik hier niet gebruiken. Elke user heeft (ongeveer) zijn eigen data, als ik voor elke update die gegevens naar memcached moet pushen word dat naar mijn idee teveel data. Behalve dat moet ik in mijn applicatie ontzettend veel aanpassen.
Ik denk dat dit wel meevalt. Je hoeft de data maar een minuut of 10 beschikbaar te hebben, daarna is de update gepusht naar al je frontend servers. Als je enkel de wijzigingen opslaat kom je misschien uit op 5 kb per user. Met twee servers heb je makkelijk een memcached cluster van 2 GB, zat ruimte voor duizenden updates.

Bovendien kan je het ook gewoon opslaan in de database, dan heb je deze limitatie niet, alleen wel meer roundtrips (maar dit zijn hele simpele queries: SELECT xmlSerialized FROM TempStorage WHERE id = 12345-692389, aangezien de echte logic in je applicatie zit). Voordeel van database is dat je data er gewoon altijd is, in cache heb je altijd het risico dat het wegraakt.

Een aanpassing hierin maken zou niet al te veel werk mogen zijn, maar ik ken je applicatieontwerp niet.
* Applicatie deels herschrijven en memcached gebruiken
Sowieso de enige les die je ooit moet onthouden als je een schaalbare website bouwt: ALLES CACHEN. Content uit je CMS: CACHEN! Resultaten van queries: CACHEN! CACHE, CACHE, CACHE.

Dankzij ons memcached cluster over 20 servers (1 GB per server) kunnen we in principe op 1 database-server 10 miljoen pagina's per dag serveren (waarvan > 1 miljoen uniek) (dat we dat niet doen is een tweede ;) )

Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 22-10 10:42
Even tussendoor; ik heb net memcached geïnstalleerd en php geconfigged... "yum install memcached" & "yum install php-pecl-memcache", "service httpd restart". Uiteraard laad hij de extensie weer niet, .so file netjes aanwezig en memcache.ini aangepast etc... maar helaas "Class 'Memcache' not found"

Gevonden: Module compiled with module API=20050922, PHP compiled with module API=20090626. These options need to match

---

Heb inmiddels memcached geïnstalleerd op de servers. Ik ga nu even experimenteren met het hele gebeuren. Het is inderdaad onzettend snel en toch vrij simpel. Nu nog even een manier vinden om dit te implementeren en dan testen! Ik houd jullie natuurlijk op de hoogte... :D

[ Voor 40% gewijzigd door TheNephilim op 15-06-2010 14:47 ]


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 22-10 10:42
Heb inmiddels al een werkend iets, heb alleen nog problemen om het allemaal een beetje netjes te krijgen. Ik werk met een db-wrapper, dus had gedacht hier iets mee te doen, maar dat is vrij bewerkelijk. Hieronder een voorbeeld van de situatie:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Nieuwe memcache
$memcache = new Memcache;
$memcache->addServer('192.168.0.7', 11211);
$memcache->addServer('192.168.0.8', 11211);

$sql = "SELECT `rider_id`,`rider_name` FROM `rider` ORDER BY `rider_name` ASC";

// Check cache
if ($memcache->get(md5($sql)))
{
  echo "<h3>Results from cache</h3>";

  echo "<pre>"; print_r ($memcache->get(md5($sql))); echo "</pre>";
}
else 
{
  // Results ophalen
  $query = db::query($sql);
  while ($row = db::fetch_assoc($query))
  {
    $result[] = $row;
  }
  
  // Push results to cache
  $memcache->set(md5($sql), $result, false, 10) or die ("Failed to save data at the server");
  
  echo "<h3>Results from db</h3>";
  
    echo "<pre>"; print_r($result); echo "</pre>";
}


Volgens mij zijn er geen alternatieven om meerdere queries in de cache te krijgen. Nadeel hiervan is een extra SELECT bij elke UPDATE. Dit moet dan na de COMMIT op de master-mysql server om te zorgen dat de data consistent is.

Een andere oplossing is na de UPDATE, de gegevens ophalen van de SLAVE en de gegevens handmatig bijwerken. Maar dit betekend dat ik per UPDATE moet gaan bepalen welke gegevens ik ga updaten (kortom wat heb ik geupdate), dit lijkt me niet de bedoeling.

Heeft iemand misschien tips? In de db-wrapper parsen en dergelijke laten regelen, of handmatig. Wat is netter?

--- UPDATE ---

Tot dusver mijn push functie:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public function push_results ($sql, $key=false, $array=array())
  {
    global $memcache;
    
    // Results ophalen
    $query = self::query($sql);
    while ($row = self::fetch_assoc($query))
    {
      if (is_array($array[$row[$key]]))
      {
        $result[] = $row;
        foreach ($array[$row[$key]] AS $key=>$value)
        {
          $row[$key] = $value;
        }
      }
      else
      {
        $result[] = $row;
      }
    }
    
    // Push to cache
    $memcache->set(md5($sql), $result, false, 10) or die ("Failed to save data at the server");
  }


Het uitlezen wil wel, maar het pushen is punt 2. Bij een update geef ik de SELECT statement mee, de key (paard_id bijv.) en welk paard id er geupdate moet worden. Ik ga eens even kijken wat ik hier mee kan...

[ Voor 28% gewijzigd door TheNephilim op 15-06-2010 16:26 ]

Pagina: 1