Facebook
Cvrlikání
Kapsa
Linkedin
fb messenger
Nedá se říci, že by 100 % firemních vtipů na Dni humoru bylo úspěšných a poutavých. V letošním roce spustila administrace Redditu Place, interaktivní grafické plátno o rozměrech 1000 na 1000 pixelů a sekci, která je mu věnovaná. Předpokládalo se, že členové komunity společně namalují toto plátno, jak se jim zlíbí. Ale v důsledku toho se to vyvinulo v bitvu o Místo, někdy přecházející ve filozofickou konfrontaci. Obyčejné kreslicí cvičení se změnilo ve vzrušující sociální experiment. Příběh od začátku do konce zdokumentoval blog Sudoscript.
Pravidla Místa byla jednoduchá. Každý účastník si mohl vybrat jeden pixel ze 16 barev a umístit jej kamkoli na plátno. Mohli jste umístit tolik pixelů, kolik jste chtěli, ale mezi každým umístěním jste museli počkat 5 minut. Po 72 hodinách toto jednoduchá pravidla vedlo k vytvoření úžasného kolektivního plátna:
Každý z výše uvedených pixelů byl umístěn ručně. Každá ikona, každá vlajka, každý mem byl pečlivě vytvořen tisíci lidí, kteří neměli nic společného kromě připojení k internetu.
Při jeho vzniku došlo k nespočtu dramat, nápadů, bojů, dokonce i válek. Ale obecně je historie Místa věčným dramatem o třech silách nutných k tomu, aby lidstvo tvořilo, tvořilo a vyvíjelo technologii.
Tvůrci
Nejprve to byli tvůrci. To byli umělci, pro které se prázdné plátno zdálo jako neodolatelná příležitost. První umělci umísťovali pixely náhodně, jen aby viděli, co dokážou. V prvních minutách se objevily první skici. Drsné a nezralé připomínaly jeskynní malby jeskynních lidí.
Tvůrci okamžitě viděli, jakou sílu a potenciál pixely skrývají. Ale pracovali sami, mohli umístit jeden pixel každých 5 nebo 10 minut. Vytvořit smysluplnou kresbu by trvalo věčnost. Aby něco nakreslili, museli spolupracovat.
Pak někoho napadlo použít pro kreslení mřížku, která by se překryla na výkres a ukázala, kde by se měly nacházet další pixely. První, kdo podstoupil tento experiment, byl slavný mem anglicky mluvícího internetu Dickbutt. A obyvatelé Place se dali do práce: Dickbutt se zhmotnil doslova během několika minut v levém dolním rohu plátna. Na stránce se objevil první výtvor kolektivní kreativity.
Když se pak tvůrci trochu opili možnostmi, objevil se Pokémon Charmander, který měl brzy místo nohy penis. A začal první konflikt: někteří tvůrci se pilně snažili uklidit útočné kresby, jiní však vytrvale přidávali obscénnosti.
Tvůrci byli postaveni před zásadní filozofický problém: Příliš mnoho svobody vede k chaosu. Kreativita potřebuje omezení stejně jako svobodu.
Obránci
V Place se objevil jiný typ uživatele, který se musel vypořádat přesně s tímto problémem. Ale začali s primitivnějšími cíli: dobýt svět. Rozděleni do frakcí podle barvy se pokusili dobýt Místo. Jedním z prvních byl Modrý kout. Vznikl v pravém dolním rohu a šířil se jako mor.
Jiná skupina založila Rudý kout na opačné straně plátna, přikláněla se k politické levici. Další skupina s názvem Green Grid malovala plátno prostřednictvím pixelů – zelených buněk proložených bílými. Protože museli malovat pouze polovinu pixelů, byli efektivnější než ostatní frakce.
Netrvalo dlouho a frakce se střetly s tvůrci. Charmander se stal jedním z prvních cílů bitvy. Modrý roh začal kreslit Pokémona modrými pixely a Tvůrci přešli z „falických válek“ (kteří mohou nakreslit nejvíce ptáků) k vážnější hrozbě. Vzali se do boje a natřeli každý modrý pixel svým vlastním. Kvantitativní výhoda ale nebyla v jejich prospěch.
Tvůrci se tedy vzdali na milost a nemilost vítězi a nějak to ranilo city Blues. Mezi nimi se objevili i ti, kteří pochybovali o své roli ve světě Místa. „Naše vlna nevyhnutelně pokryje celý svět, od konce do konce, pokud prokážeme milosrdenství jinému umění, které potkáme,“ zeptal se jeden ze skupiny.
Každá frakce čelila této otázce. A všichni se rozhodli uložit další kresby. Vlny barev tedy začaly obtékat kresby, aniž by je malovaly.
Tohle byl zlom. Bezduché barevné frakce se staly užitečnými obránci.
Ale šťastný konec to zatím není
Konečně byly neukojitelné vlny barev zastaveny a Tvůrci se mohli vrátit ke kreativitě. Kresby byly stále složitější. Objevily se texty psané v pixelech.
Tvůrci vytvořili malé skupiny a vytvořili subreddity, kde mohli diskutovat o návrhu umění a strategii. Jedna z nejúspěšnějších skupin nakreslila hlavní panel ve stylu Windows 95. Další načrtla Místo se srdíčky.
Pak se objevil Van Gogh.
Ale nebylo to tak jednoduché. Obránci se proměnili v tyrany, diktující styl kreseb. Rozhodovali, co se dá kreslit a co ne. Frakce si mezi sebou začaly rozdělovat uživatele a volaly po stranách, zatímco Tvůrci čekali na schválení nových nápadů.
Boje mezi obránci byly stále zuřivější. Jeden streamer na Twitchi vyzval své následovníky, aby zaútočili na Blue. Byly vyvinuty bojové strategie. Došlo dokonce k provokacím: fanoušci stejné barvy sami namalovali pixely nepřátelské barvy na své území, aby měli omluvu pro odvetný útok. Zatímco frakce mezi sebou bojovaly, Tvůrci zjistili, že nezbývá místo pro nové kresby.
Začaly se objevovat vlajky různé země– jak rostli, nevyhnutelně do sebe naráželi. Například vlajky Německa a Francie se srazily v „zemi nikoho“.
Zdálo se, že tento malý svět je na pokraji války. Všechny strany se snažily konflikt vyřešit diplomaticky. Lídři Tvůrců a Obránců spolu komunikovali v chatech, ale většinou to skončilo vzájemným obviňováním.
Místo potřebovalo padoucha, proti kterému by se všichni ostatní mohli spojit.
Torpédoborce
Přišla prázdnota.
Začalo to 4chan, nejslavnějším imageboardem na světě. Žertíci, kteří ji obývali, si všimli, co se děje na Redditu, a nemohli to ignorovat. Stali se Prázdnotou.
Uprostřed Místa začala růst skvrna černých pixelů. Nejprve se frakce pokusily pomocí diplomacie uzavřít pakt s Prázdnotou. Ale neuspěli, Prázdnota jednala jinak. Nepatřila mezi ochránce, neštítila se umění. Její následovníci kázali, že Prázdnota pohltí všechno. Netvořili strany, jen chtěli natřít celý svět na černo.
Tohle byl přesně ten kopanec do zadku, který Místo potřebovalo. Tváří v tvář společné hrozbě se Tvůrci a obránci znovu spojili, aby zachránili umění. Ale účelem Prázdnoty nebyla jen destrukce, nějak to dalo vzniknout novému, lepšímu umění.
Například pozice ve středu byla mezi tvůrci jednou z nejspornějších. A když zčernalo, Obránci si uvědomili, že budou muset přijít s lepším nápadem, který by přilákal dostatek následovníků k boji s černou příšerou. Jedním z těchto nápadů byla americká vlajka.
Poslední den existence Place v něm vznikla ta nejneuvěřitelnější koalice určená k boji s Prázdnotou – byli tam Trumpovi fanoušci i Trumpovi odpůrci, republikáni i demokraté, Američané i Evropané.
Experiment Reddit brzy skončil. Na finálním plátně nebyla jediná rasistická kresba ani jediný symbol nenávisti.
Cvrlikání
Kapsa
Linkedin
fb messenger
Pro začátek bylo nesmírně důležité určit požadavky na aprílový projekt, protože musel být spuštěn bez „přetaktování“, aby k němu měli okamžitě přístup všichni uživatelé Redditu. Kdyby to nefungovalo dokonale od samého začátku, jen stěží by to přitáhlo pozornost mnoha lidí.
"Tabulka" by měla mít velikost 1000x1000 dlaždic, aby vypadala velmi velká.
Všichni klienti by měli být synchronizováni a měli by zobrazovat stejný stav desky. Koneckonců, pokud mají různí uživatelé různé verze, bude pro ně obtížné komunikovat.
Musíte podporovat alespoň 100 000 souběžných uživatelů.
Uživatelé mohou umístit jednu dlaždici každých pět minut. Proto je nutné udržovat průměrnou rychlost aktualizace 100 000 dlaždic za pět minut (333 aktualizací za sekundu).
Projekt by neměl negativně ovlivnit provoz ostatních částí a funkcí lokality (i když je na r/Place vysoká návštěvnost).
- Musí být zajištěna flexibilní konfigurace pro případ neočekávaných úzkých profilů nebo selhání. To znamená, že musíte být schopni upravit velikost desky a povolenou frekvenci kreslení za běhu, pokud je množství dat příliš velké nebo frekvence aktualizací příliš vysoká.
Backend
Implementační řešení
Hlavním problémem při vytváření backendu byla synchronizace zobrazení stavu desky pro všechny klienty. Bylo rozhodnuto, aby klienti naslouchali událostem umístění dlaždic v reálném čase a okamžitě se dotazovali na stav celé desky. Mírně zastaralý úplný stav je přijatelný, pokud jste se přihlásili k odběru aktualizací před vygenerováním úplného stavu. Když klient obdrží úplný stav, zobrazí všechny dlaždice, které obdržel během čekání; všechny následující destičky se musí objevit na herním plánu, jakmile je obdrží.
Aby toto schéma fungovalo, požadavek plný stav desky by měly být dokončeny co nejrychleji. Původně jsme chtěli uložit celou desku do jednoho řádku v Cassandře a nechat každý požadavek jednoduše přečíst tento řádek. Formát každého sloupce v tomto řádku byl:
(x, y): ('timestamp': epochms, 'author': user_name, 'color': color)
Ale protože deska obsahuje milion dlaždic, potřebovali jsme přečíst milion sloupců. Na našem produkčním clusteru to trvalo až 30 sekund, což bylo nepřijatelné a mohlo by to mít za následek nadměrné zatížení Cassandry.
Pak jsme se rozhodli celou desku uložit do Redisu. Vzali jsme bitové pole milionu čtyřbitových čísel, z nichž každé mohlo zakódovat čtyřbitovou barvu, a souřadnice x a y byly určeny posunem (offset = x + 1000y) v bitovém poli. Pro získání úplného stavu desky bylo nutné přečíst celé bitové pole.
Dlaždice bylo možné aktualizovat aktualizací hodnot s určitými posuny (není třeba blokovat nebo provádět celou proceduru čtení/aktualizace/zápisu). Všechny detaily ale musí být stále uloženy v Cassandře, aby uživatelé mohli zjistit, kdo a kdy umístil jednotlivé dlaždice. Také jsme plánovali použít Cassandru k obnovení desky, když Redis havaroval. Načtení celé desky z ní trvalo necelých 100 ms, což bylo celkem rychlé.
Zde je návod, jak jsme uložili barvy v Redis pomocí příkladu desky 2x2:
Báli jsme se, že bychom mohli narazit na propustnost čtení v Redis. Pokud by se současně připojilo nebo aktualizovalo mnoho klientů, všichni by současně odeslali požadavky na úplný stav desky. Protože deska představovala sdílený globální stav, zřejmým řešením bylo použití mezipaměti. Rozhodli jsme se ukládat do mezipaměti na úrovni CDN (Fastly), protože to bylo jednodušší na implementaci a mezipaměť byla získána nejblíže klientům, což zkrátilo čas na přijetí odpovědi.
Požadavky na stav celé desky byly ukládány do mezipaměti pomocí Fastly s časovým limitem jedné sekundy. Aby se zabránilo velký počet požadavky, když vypršel časový limit, použili jsme hlavičku stale-while-revalidate. Rychle podporuje asi 33 POPs, které se navzájem nezávisle ukládají do mezipaměti, takže jsme očekávali, že obdržíme až 33 žádostí o stav celé desky za sekundu.
Ke zveřejnění aktualizací všem klientům jsme použili naši službu websocket. Dříve jsme jej úspěšně používali k napájení Reddit.Live s více než 100 000 souběžnými uživateli pro oznámení soukromých zpráv Live a další funkce. Služba byla také základním kamenem našich minulých aprílových projektů The Button a Robin. V případě r/Place klienti podporovali připojení websocket pro příjem aktualizací v reálném čase o umístění dlaždic.
API
Získání stavu plné penze
Nejprve žádosti směřovaly do Fastly. Pokud by měl platnou kopii desky, okamžitě by ji vrátil, aniž by kontaktoval aplikační servery Redditu. Pokud ne, nebo je kopie příliš stará, pak aplikace Reddit přečte celou nástěnku z Redis a vrátí ji do Fastly, aby byla uložena do mezipaměti a vrácena klientovi.
Všimněte si, že rychlost požadavků nikdy nedosáhla 33 za sekundu, což znamená, že ukládání do mezipaměti s Fastly bylo velmi účinnými prostředky Ochrana aplikace Reddit před většinou požadavků.
A když se žádosti dostaly do aplikace, Redis reagoval velmi rychle.
Kreslení dlaždice
Fáze kreslení dlaždice:
- Časové razítko posledního umístění dlaždice uživatelem je načteno z Cassandry. Pokud to bylo před méně než pěti minutami, neděláme nic a uživateli se vrátí chyba.
- Podrobnosti o dlaždici jsou zapsány Redis a Cassandře.
- Aktuální čas je v Cassandře zaznamenán jako poslední, kdy uživatel umístil dlaždici.
- Služba websocket odešle zprávu o nové dlaždici všem připojeným klientům.
Pro zachování přísné konzistence byly všechny zápisy a čtení v Cassandře prováděny pomocí vrstvy konzistence QUORUM.
Ve skutečnosti jsme zde měli závod, kde uživatelé mohli umístit více dlaždic najednou. Ve fázích 1–3 nedošlo k žádnému blokování, takže současné pokusy o tažení destiček mohly projít kontrolou v první fázi a být taženy ve druhé. Zdá se, že někteří uživatelé tuto chybu objevili (nebo použili roboty, kteří ignorovali limit frekvence požadavků) - a ve výsledku bylo pomocí ní umístěno asi 15 000 dlaždic (~0,09 % z celkového počtu).
Rychlosti požadavků a doby odezvy měřené aplikací Reddit:
Maximální rychlost umístění dlaždic byla téměř 200 za sekundu. To je pod naším odhadovaným limitem 333 dlaždic/s (průměr za předpokladu, že 100 000 uživatelů umístí dlaždice každých pět minut).
Získání podrobností pro konkrétní dlaždici
Při požadavku na konkrétní dlaždice byla data načtena přímo z Cassandry.
Rychlosti požadavků a doby odezvy měřené aplikací Reddit:
Tato žádost se ukázala být velmi populární. Kromě běžných požadavků klientů lidé napsali skripty, aby získali celou desku po jednotlivých dlaždicích. Protože tento požadavek nebyl uložen do mezipaměti v CDN, všechny požadavky obsluhovala aplikace Reddit.
Doba odezvy na tyto požadavky byla poměrně krátká a zůstala na stejné úrovni po celou dobu trvání projektu.
Websockets
Nemáme jednotlivé metriky ukazující, jak r/Place ovlivnilo službu websocket. Hodnoty však můžeme odhadnout porovnáním dat před zahájením projektu a po jeho dokončení.
Celkový počet připojení ke službě websocket:
Základní zatížení před spuštěním r/Place bylo asi 20 000 spojení, vrchol byl 100 000 spojení. Takže na vrcholu jsme pravděpodobně měli asi 80 000 souběžných uživatelů připojených k r/Place.
Propustnost služby Websocket:
Na vrcholu zatížení na r/Place přenesla služba websocket více než 4 Gbps (150 Mbps na instanci, celkem 24 instancí).
Frontend: weboví a mobilní klienti
V procesu vytváření frontendu pro Place jsme se museli hodně rozhodnout složité úkoly související s vývojem napříč platformami. Chtěli jsme, aby projekt fungoval stejně na všech hlavních platformách, včetně desktopů a mobilní zařízení na iOS a Android.
Uživatelské rozhraní muselo plnit tři důležité funkce:
- Zobrazení stavu desky v reálném čase.
- Umožněte uživatelům komunikovat s tabulí.
- Práce na všech platformách včetně mobilních aplikací.
Hlavním objektem rozhraní bylo plátno a rozhraní Canvas API bylo pro něj ideální. Použili jsme prvek
Kreslení plátna
Plátno muselo odrážet stav desky v reálném čase. Po načtení stránky bylo nutné nakreslit celou desku a dokreslit aktualizace přicházející přes websockety. Prvek plátna, který používá rozhraní CanvasRenderingContext2D, lze aktualizovat třemi způsoby:
- Nakreslete existující obrázek na plátno pomocí drawImage() .
- Kreslit tvary pomocí různé metody výkresové formy. Například fillRect() vyplní obdélník nějakou barvou.
- Vytvořte objekt ImageData a nakreslete jej na plátno pomocí putImageData() .
První možnost nám nevyhovovala, protože jsme neměli desku v podobě hotového obrázku. Zbyly tak možnosti 2 a 3. Nejjednodušším způsobem bylo aktualizovat jednotlivé dlaždice pomocí fillRect() : když aktualizace dorazí přes websocket, jednoduše nakreslíme obdélník 1x1 na pozici (x, y). Obecně metoda fungovala, ale nebyla příliš vhodná pro kreslení počátečního stavu desky. Metoda putImageData() byla mnohem lepší: mohli jsme určit barvu každého pixelu v jediném objektu ImageData a nakreslit celé plátno najednou.
Kreslení počátečního stavu desky
Použití putImageData() vyžaduje definování stavu desky jako Uint8ClampedArray , kde každá hodnota je osmibitové číslo bez znaménka v rozsahu 0 až 255. Každá hodnota představuje barevný kanál (červený, zelený, modrý, alfa) a pixel vyžaduje čtyři prvky v poli. Plátno 2x2 vyžaduje 16bajtové pole, ve kterém první čtyři bajty představují levý horní pixel plátna a poslední čtyři představují pixel vpravo dole.
Zde je návod, jak jsou pixely plátna spojeny s jejich reprezentacemi Uint8ClampedArray:
Pro plátno našeho projektu jsme potřebovali pole čtyř milionů bajtů – 4 MB.
V backendu je stav desky uložen jako čtyřbitové bitové pole. Každá barva je reprezentována číslem od 0 do 15, což nám umožnilo zabalit dva pixely do každého bajtu. Chcete-li to použít na klientském zařízení, musíte udělat tři věci:
- Přeneste binární data z našeho API do klienta.
- Rozbalte data.
- Převeďte čtyřbitové barvy na 32bitové.
K přenosu binárních dat jsme použili Fetch API v těch prohlížečích, které to podporují. A v těch, které nepodporují, jsme použili XMLHttpRequest s responseType nastaveným na „arraybuffer“ .
Binární data přijatá z API obsahují dva pixely v každém bajtu. Většina malý konstruktér TypedArray, který jsme měli, nám umožňuje pracovat s binárními daty ve formě jednobajtových jednotek. Na klientských zařízeních se ale obtížně používají, proto jsme data rozbalili, abychom si s nimi usnadnili práci. Proces je jednoduchý: iterovali jsme zabalená data, vytáhli bity vyššího a nižšího řádu a poté je zkopírovali do jednotlivých bajtů do jiného pole.
Nakonec musely být čtyřbitové barvy převedeny na 32bitové.
Struktura ImageData, kterou jsme potřebovali k použití putImageData(), to vyžaduje konečný výsledek byl ve formě Uint8ClampedArray s bajty kódujícími barevné kanály v pořadí RGBA. To znamená, že jsme museli provést další dekompresi, rozdělit každou barvu na jednotlivé bajty kanálu a umístit je do správného indexu. Není příliš vhodné provádět čtyři zápisy na pixel. Ale naštěstí tu byla i jiná možnost.
Objekty TypedArray jsou v podstatě reprezentace pole ArrayBuffer. Je zde jedno upozornění: více instancí TypedArray může číst a zapisovat do stejné instance ArrayBuffer. Místo nahrávání čtyři hodnoty v osmibitovém poli můžeme zapsat jednu hodnotu do 32bitového! Pomocí Uint32Array pro zápis jsme byli schopni snadno aktualizovat barvy dlaždic jednoduchou aktualizací jednoho indexu pole. Museli jsme však uložit naši barevnou paletu ve velkém pořadí bajtů (ABGR), aby bajty automaticky spadaly do správná místa při čtení pomocí Uint8ClampedArray .
Zpracování aktualizací přijatých přes websocket
Metoda drawRect() byla dobrá pro vykreslování aktualizací jednotlivých pixelů tak, jak byly přijaty, ale byla tu jedna věc slabé místo: Velké dávky aktualizací přicházející současně mohou vést ke zpomalení prohlížečů. A pochopili jsme, že aktualizace stavu desky mohou přicházet velmi často, takže se problém musel nějak vyřešit.
Místo okamžitého překreslování plátna pokaždé, když je aktualizace přijata přes websocket, rozhodli jsme se to udělat tak, aby aktualizace websocketu, které dorazí ve stejnou dobu, mohly být dávkovány a vykreslovány hromadně. Aby toho bylo dosaženo, byly provedeny dvě změny:
- Přestat používat drawRect() - našli jsme pohodlný způsob aktualizujte mnoho pixelů najednou pomocí putImageData() .
- Přenos vykreslování plátna do smyčky requestAnimationFrame.
Přesunutím vykreslování do animační smyčky jsme byli schopni okamžitě zapisovat aktualizace webového soketu do ArrayBufferu, zatímco skutečné vykreslování bylo odloženo. Všechny aktualizace websocketu přicházející mezi snímky (asi 16 ms) byly dávkovány a vykresleny současně. Díky použití requestAnimationFrame , pokud by vykreslování trvalo příliš dlouho (déle než 16 ms), mělo by to vliv pouze na obnovovací frekvenci plátna (spíše než na snížení výkonu celého prohlížeče).
Interakce s plátnem
Je důležité poznamenat, že plátno bylo potřeba, aby bylo pro uživatele pohodlnější interakce se systémem. Hlavním scénářem interakce je umístění dlaždic na plátno.
Ale udělat přesné vykreslení každého pixelu v měřítku 1:1 by bylo extrémně obtížné a nevyvarovali bychom se chyb. Takže jsme potřebovali (velký!) zoom. Uživatelé navíc potřebovali, aby mohli snadno procházet plátnem, protože bylo pro většinu obrazovek příliš velké (zejména při použití zoomu).
Přiblížení
Vzhledem k tomu, že uživatelé mohli umístit dlaždice jednou za pět minut, byly by pro ně chyby umístění obzvláště frustrující. Bylo nutné implementovat zoom takového faktoru, aby dlaždice byla dostatečně velká a dala se do ní snadno umístit správné místo. To bylo důležité zejména u zařízení s dotykovou obrazovkou.
Implementovali jsme 40x zoom, to znamená, že každá dlaždice měla velikost 40x40. Zabalili jsme prvek