The rules were simple. Each user could choose one of 16 colors and paint one pixel anywhere on the canvas with it. It was possible to paint as many pixels as you wanted and with whatever colors you wanted, but in order to recolor the next pixel, you had to wait 5 minutes.
True, the rules said: “By coordinating with others, you can create much more than by acting alone.”
What happened over the next 72 hours shocked the organizers. This appeared on an empty canvas:
Each pixel on the canvas was placed manually. Every icon, every flag, every meme was painstakingly created by hundreds of thousands of people who had nothing to do with each other except an internet connection. So, one way or another, but what happened on Reddit can rightly be considered the birth of art.
How it all happened
It is impossible to describe it in a few words. Countless dramas took place on the canvas - fights, battles and wars, sometimes it’s not even clear for what reason. They were conducted on small forums, in private chats, there were so many of them and they all happened at once, so it was not possible to keep track of everything. In general, the canvas was traced eternal history about the three forces necessary for humanity to create.
Creators
The creators came first. They were artists for whom the pure canvas has an irresistible attraction.
The creators began to recolor the pixels randomly, just to see what they could do. Therefore, the first drawings looked more like rock art– the artists were just beginning to spread their wings.
Pretty quickly, they realized that working alone and placing only one pixel every 5-10 minutes, it is impossible to create anything significant. Someone is bound to ruin their work. To create something more, they must work together.
And then someone suggested drawing on a grid that would clearly show where the next pixel needed to be painted in order to get a coherent image. So in the lower left part of the canvas appeared Dickbutt - a famous Internet meme, the fruit of a teenage sense of humor. It became the first joint work.
But the creators did not stop there. They began to add various elements to Dickbutt, paint it in different colors and even tried to transform it into Dickbutterfly. Behind this stupid idea was a hint of an impending creative tsunami.
However, this did not happen immediately. The creators were intoxicated by their power. Next to Dickbutt, a Pokemon Charmander appeared, in which, instead of a paw, a member began to grow, and then two more.
It was no longer a design. Some creators have tried desperately to remove the provocative additions, calling for "pure" art, but others have persisted. But it was not there.
It became clear that too much freedom leads to chaos. Creativity needs limits just as much as it needs freedom. When someone can put any pixel anywhere, how can that not lead to mayhem?
Guardians
This problem was very quickly solved by another type of users - keepers. They came with one goal - to conquer the whole world.
Having formed fractions by color, they began to conquer space by painting it in a certain color. One of the first and largest was the Blue Corner faction. Appearing in the lower right corner, it spread like a plague. Her followers proclaimed that in this way they should curl the entire space of the canvas. Pixel by pixel, they began to translate their idea into reality, capturing huge areas soon.
Blue Corner was not alone in its endeavors. On the other side of the canvas, another group appeared - Red Corner (red corner). Its participants said that they are adherents of the left political views. Another group - Green Lattice (green lattice) - took up the ubiquitous interspersing of green and white pixels. It proved to be highly efficient, as it had to paint half as many pixels as the other factions.
Keepers went to the creators in a frontal attack. Charmander became the first site of the battle. Upon discovering that the Blue Corner had begun slaughtering the Pokémon with blue pixels, the creators realized the threat and ended the internecine wars.
They fought back, replacing every blue pixel with their own. But the forces were not equal. Thanks to his determination, Blue Corner has gathered a much larger army than the creators. And the only thing left for the creators to do in such a situation was to beg for their lives.
And somehow it turned the tide. At the Blue Corner, a debate began about their role in creative process. One participant posed the question, “Since our wave inevitably takes over the world completely, should we show mercy to other art forms that we encounter?”
It was a question that sooner or later confronted every faction. For all their expansionist zeal, what were they to do with the art that stood in their way?
It has become turning point. Mindless factions turned into defenders.
But it wasn't over yet
In a world filled with predatory color, the creators were able to return to their creations. By adding one element after another, they began to make them more complex. Using three-pixel fonts, they began to write texts. One of the most famous creations was the Star Wars prequel.
The creators united in groups working on a common project. They shared strategies and patterns among themselves. One of the most successful was the group that created the Windows 95 panel with the Start button in the corner.
Others have created a block of hearts like in old video games like Zelda. Few started this project, but others quickly joined them, and as a result, hearts, painted in the colors of various flags, stretched to half the canvas.
Another group recreated Van Gogh's Starry Night.
However, not everything went smoothly. Defenders who once welcomed the creation of works of art have become fashion tyrants. They began to specify what can be created and what not. It began shortly before the creators began to create according to their own rules.
The factions turned their eyes on each other, demanding that their followers take sides in epic battles. They didn't have time to pay attention to the pitiful pleas of the creators who wanted to get approval for the ideas of the new art.
Fighting between the defenders flared up serious. Twitch live-streamers encouraged their followers to attack Blue Corner and Purple. Battle plans were being made. They called for emotions.
There have even been feint attacks, where adherents of the same color placed opponent pixels inside their own so that they can complain about the violation and attack back.
However, the biggest problem was a strict rule - the canvas cannot be enlarged. Both the warring factions and the creators began to realize that they simply would not have room for new art.
From the very beginning, flags appeared on the canvas various countries. They grew and bumped into each other. A real epic battle broke out between the flags of Germany and France. It became clear that an intermediary was needed to develop new spaces.
Suddenly, the world, having escaped primitive raids at the beginning, became ready for full-scale war. Desperate attempts to solve the problem through diplomacy came to nothing. Meeting in chats, the leaders of the creators and defenders only blamed each other.
We needed a slicker with whom everyone could agree.
Destroyers
On the Internet site 4chan drew attention to what was happening on Reddit. And they couldn't get past. Their users have chosen the color closest to their heart - black. They became the Void.
As a tear slowly spreads over the surface, so black pixels began to appear in the center of the canvas, destroying everything in its path.
At first, other factions tried to make an alliance with them, naively believing that diplomacy would work. But they failed because the Void was different.
The void was no protector. Unlike other factions, she did not show any loyalty to the arts. The followers of the Void practiced destructive egalitarianism under the slogan "The Void will swallow everything." They didn't make contact with others. They just wanted to paint the whole world black.
And that was exactly what was required. On the verge of extinction, all the members of the project banded together to fight the Void to save their art.
But the Void was not so easy to defeat, because it was needed. It was necessary to destroy everything so that a new art, the best, would be reborn from the ashes. And without the Void it was impossible.
So the Void became the catalyst for creation the largest work art.
From the very beginning there was a stubborn struggle for the central part of the canvas. The creators claimed this territory for their works. At first they tried to do it with the help of icons. Then in a coordinated attempt to create a prism like on the cover Album Pink Floyd back side Moon."
But the Void ate everything. One after another, the creations created only warmed up her predatory appetite for chaos.
And yet, it was exactly what was needed. By destroying the art, The Void forced users to come up with something better. They knew they could defeat the black monster. They just need an idea with good potential that will attract enough followers.
And that idea was the American flag.
On the last day of the project, everyone came together to drive the void away once and for all. A coalition was created from people who would otherwise tear each other apart - from supporters and opponents of Trump, from Democrats and Republicans, from Americans and Europeans.
They teamed up to create something together, in this little corner of the internet, proving that in an era where such collaboration seems impossible, they can still do it.
The ancients were right
Shortly thereafter, the Reddit experiment ended. Today, he is accompanied by many stories told in dozens of chat rooms. Each work of art created in the project was covered by hundreds of new ones, of which only a few remained on the final canvas.
But the most surprising, perhaps, is that, despite the anonymity and lack of prohibitions, there were no racist or misanthropic symbols on the final canvas. It was a beautiful circuit of art, life and death. And he was not the first in our history.
Many millennia ago, when humanity (the real one, not just the one on Reddit) was still in its infancy, Hindu philosophers suggested that the heavens were made up of three competing, but necessary, deities: Brahma the Creator, Vishnu the Preserver, and Shiva- Destroyer.
Even without one of them, the universe would not be able to function. For there to be light, there must be darkness. For life to exist, death is needed. For creation and art there must be destruction.
Several days of the project showed that this approach proved to be prophetic. In the most incredible way, Reddit proved that creation requires the presence of all three components.
Final canvas
fb messenger
It cannot be said that 100% of corporate jokes on Humor Day are successful and engaging. This year, the Reddit administration launched Place, an interactive 1000 by 1000 pixel graphic canvas, and a section dedicated to it. It was assumed that the members of the community would jointly paint this canvas as they liked. But as a result, it turned into a battle for the Place, sometimes turning into a philosophical confrontation. An ordinary drawing exercise has turned into an exciting social experiment. The story from start to finish was documented by the Sudoscript blog.
The rules of the Place were simple. Each participant could choose one pixel from 16 colors and place it anywhere on the canvas. You could place as many pixels as you wanted, but you had to wait 5 minutes between each placement. After 72 hours, these very simple rules led to the creation of an amazing collective canvas:
Each of the pixels visible above was placed manually. Every icon, every flag, every meme was painstakingly created by thousands of people who had nothing in common but an internet connection.
During creation, there were countless dramas, ideas, fights, even wars. But in general, the history of the Place is an eternal drama about the three forces that humanity needs to create and create and develop technologies.
Creators
First there were the creators. These were artists for whom the empty canvas seemed like an irresistible opportunity. Early artists placed pixels randomly, just to see what could be done. In the first minutes, the first sketches appeared. Rough and immature, they resembled the cave paintings of cavemen.
The creators immediately saw what power and potential the pixels hide. But working alone, they could place one pixel every 5 or 10 minutes. Creating a meaningful drawing would take forever. To draw something, they had to work together.
Then someone came up with the brilliant idea of using a grid for drawing, which would overlay the drawing and show where the next pixels should be. The first to go through this experiment was the well-known meme of the English-speaking Internet Dickbutt. And the inhabitants of the Place set to work: Dickbutt materialized in just minutes in the lower left corner of the canvas. The first creation of collective creativity appeared at the Place.
Then, when the creators got a little drunk on the possibilities, the Pokemon Charmander appeared, in which a member rather soon appeared instead of a leg. And the first conflict began: some creators diligently tried to clean up offensive drawings, but others persistently added obscene things.
The creators are faced with a fundamental philosophical problem: too much freedom leads to chaos. Creativity needs a limiter just as it needs freedom.
Defenders
The Place had a different type of user who had to deal with exactly this problem. But they started with more primitive goals: the conquest of the world. Dividing into factions by color, they tried to capture the Place. One of the first was the Blue Corner. It originated in the lower right corner and spread like a plague.
Another group founded the Red Corner on the opposite side of the canvas, they leaned towards the political left. Another group called the Green Grid painted the canvas through the pixel - green cells interspersed with white. Since they only had to paint half the pixels, they were more efficient than the other factions.
It wasn't long before the factions clashed with the creators. Charmander became one of the first objects of the battle. The blue corner began painting the Pokémon with blue pixels, and the Creators switched from "phallic wars" (who draws more members) to a more serious threat. They took the fight, painting every blue pixel with their own. But the quantitative advantage was not in their favor.
So the Creators surrendered to the mercy of the winner, and somehow it hurt the feelings of the Blues. Among them appeared doubting their role in the world of the Place. “Our wave will inevitably cover the whole world, from edge to edge, should we show mercy to other art that we encounter,” asked one of the group.
Each of the factions faced this issue. And everyone decided to save other drawings. So the colored waves began to flow around the drawings without painting over them.
This was the turning point. Mindless colored factions have become useful Defenders.
But it's not a happy ending
Finally, the insatiable color waves were stopped and the Creators could return to creativity. The drawings became more and more difficult. Texts written in pixels appeared.
The creators united small groups, creating subsections on Reddit where draft drawings and strategy could be discussed. One of the most successful groups drew a taskbar in the style of Windows 95. Another sketched the Place with hearts.
Then came Van Gogh.
But everything was not so simple. The defenders turned into tyrants, dictating the style of the drawings. They decide what can be drawn and what is not. Factions began to divide users among themselves, calling for parties, meanwhile the Creators were waiting for the approval of new ideas.
The battles between the Defenders were getting tougher. One Twitch streamer called on his followers to attack the BLUs. Battle strategies were developed. There were even provocations: fans of the same color themselves drew pixels of the enemy's color on their territory in order to have an excuse for a retaliatory attack. While the factions fought among themselves, the Creators found that there was no room for new drawings.
The flags began to rise different countries As they grow, they inevitably run into each other. For example, the flags of Germany and France clashed on "no man's" territory.
The world seemed to be on the brink of war. All parties tried to resolve the conflict through diplomacy. The leaders of the Creators and Defenders chatted, but usually ended in mutual accusations.
The place needed a villain that everyone else could unite against.
Destroyers
The void has arrived.
It started with 4chan, the most famous image board in the world. The pranksters inhabiting it noticed what was happening on Reddit and could not pass by. They became the Void.
A spot of black pixels began to grow in the center of the Place. At first, the factions tried to make a pact with the Void through diplomacy. But they failed, the Void acted differently. She was not one of the Defenders, she was not guarding art. Her followers preached that the Void would devour everything. They didn't form sides, they just wanted to paint the whole world black.
It was exactly the kick in the ass that the Place lacked. Faced with a common threat, the Creators and Defenders once again united to save art. But the meaning of the Void was not just destruction, somehow it gave rise to a new, better art.
For example, the location in the center was one of the most contested among the creators. And when it turned black, the Defenders realized that they would have to come up with a better idea that would involve enough followers to fight the black monster. One of these ideas was the US flag.
On the last day of the Place, the most incredible coalition formed to fight the Void—Trump fans and Trump detractors, Republicans and Democrats, Americans and Europeans.
Soon the Reddit experiment ended. There was not a single racist drawing on the final canvas, not a single symbol of hatred.
fb messenger
To begin with, it was extremely important to determine the requirements for the April Fools project, because it had to be launched without “overclocking” so that all Reddit users would immediately have access to it. If it had not worked perfectly from the very beginning, it would hardly have attracted the attention of a large number of people.
- In case of unexpected bottlenecks or failures, flexible configuration must be provided. That is, you need to be able to adjust the size of the board and the allowed drawing frequency on the fly if the amount of data is too large or the refresh rate is too high.
The "board" needs to be 1000x1000 tiles in order to look very large.
All clients must be in sync and display the same board state. After all, if different users have different versions, it will be difficult for them to interact.
You need to support at least 100,000 users at the same time.
Users can place one tile every five minutes. Therefore, it is necessary to maintain an average update rate of 100,000 tiles per five minutes (333 updates per second).
The project should not negatively affect the work of other parts and functions of the site (even if there is high traffic on r/Place).
Backend
Implementation decisions
The main difficulty in creating the backend was to synchronize the display of the state of the board for all clients. It was decided to have clients listen for tile placement events in real time and immediately request the state of the entire board. It is acceptable to have a slightly outdated full state if you subscribe to updates before the full state was generated. When the client receives the full state, it displays all the tiles it received while waiting; all subsequent tiles must be displayed on the board as soon as they are received.
For this scheme to work, the request full state boards should run as fast as possible. At first, we wanted to store the entire board on one line in Cassandra, and have each request just read that line. The format for each column in this row was:
(x, y): ('timestamp': epochms, 'author': user_name, 'color': color)
But since the board contains a million tiles, we needed to read a million columns. On our production cluster, this took up to 30 seconds, which was unacceptable and could lead to excessive load on Cassandra.
Then we decided to store the entire board in Redis. We took a bit field of a million four-bit numbers, each of which could encode a four-bit color, and the x and y coordinates were determined by the offset (offset = x + 1000y) in the bit field. To get the full state of the board, it was necessary to read the entire bit field.
Tiles could be updated by updating values at specific offsets (no need to block or do the whole read/update/write procedure). But all the details still need to be stored in Cassandra so that users can find out who posted each of the tiles and when. We also planned to use Cassandra to restore the board when Redis crashed. Reading the entire board from it took less than 100 ms, which was quite fast.
Here's how we stored colors in Redis using a 2x2 board as an example:
We were worried that we might run into read throughput on Redis. If many clients were connecting or updating at the same time, then all of them simultaneously sent requests to get the full state of the board. Since the board was a shared global state, the obvious solution was to use caching. We decided to cache at the CDN level (Fastly), because it was easier to implement, and the cache was closest to the clients, which reduced the response time.
Full board state requests were cached by Fastly with a timeout per second. To prevent a large number of requests when the timeout expires, we have used the stale-while-revalidate header. Fastly supports about 33 POPs that cache independently, so we expected to receive up to 33 full board state requests per second.
To publish updates to all clients, we used our websocket service. We have previously used it successfully to power Reddit.Live with over 100,000 concurrent users for live private message notifications and other features. The service was also cornerstone our past April Fools' projects - The Button and Robin. In the case of r/Place, clients supported websocket connections to receive real-time updates on tile placements.
API
Getting the full state of the board
At first requests got to Fastly. If it had a valid copy of the board, then it immediately returned it without contacting the Reddit application servers. If not, or the copy was too old, then the Reddit application read the full board from Redis and returned it to Fastly to be cached and returned to the client.
Please note that the request rate never reached 33 per second, i.e. caching with Fastly was very effective tool protecting the Reddit app from most requests.
And when requests did reach the application, Redis responded very quickly.
Tile drawing
Stages of drawing a tile:
- The timestamp of the last tile placed by the user is read from Cassandra. If it was less than five minutes ago, then we do nothing and an error is returned to the user.
- Tile details are written to Redis and Cassandra.
- The current time is recorded in Cassandra as the last time the user placed a tile.
- The websocket service sends a message about the new tile to all connected clients.
In order to maintain strict consistency, all writes and reads in Cassandra were performed using the consistent level QUORUM .
In fact, we had a race here whereby users could place multiple tiles at once. There was no blocking in stages 1-3, so simultaneous attempts to draw tiles could pass the test in the first stage and be drawn in the second. It seems that some users found this bug (or they used bots that ignored the limit on the frequency of sending requests) - and as a result, about 15,000 tiles were placed using it (~0.09% of the total).
Request rate and response time as measured by the Reddit app:
The peak tile placement rate was almost 200 per second. This is below our calculated limit of 333 tiles/s (average assuming 100,000 users place their tiles every five minutes).
Getting details on a specific tile
When requesting specific tiles, data was read directly from Cassandra.
Request rate and response time as measured by the Reddit app:
This request proved to be very popular. In addition to regular client requests, people have written scripts to retrieve the entire board one tile at a time. Since this request was not cached in the CDN, all requests were served by the Reddit application.
The response time to these requests was quite short and kept at the same level throughout the life of the project.
Websockets
We don't have separate metrics showing how r/Place has affected the performance of the websocket service. But we can estimate the values by comparing the data before the start of the project and after its completion.
Total number of connections to the websocket service:
The base load before the launch of r/Place was about 20,000 connections, the peak was 100,000 connections. So at the peak we probably had about 80,000 users connected to r/Place at the same time.
Throughput of the websocket service:
At peak load on r/Place, the websocket service was transmitting over 4 Gbps (150 Mbps per instance, 24 instances in total).
Frontend: web and mobile clients
In the process of creating the front-end for Place, we had to solve many complex tasks related to cross-platform development. We wanted the project to work the same on all major platforms, including desktop PCs and mobile devices on iOS and Android.
The user interface had to perform three important functions:
- Display board status in real time.
- Allow users to interact with the board.
- Work on all platforms, including mobile applications.
The main object of the interface was the canvas, and the Canvas API was perfect for it. We have used the element
Canvas drawing
The canvas had to reflect the state of the board in real time. It was necessary to draw the entire board when the page loaded and finish drawing updates coming through web sockets. A canvas element that uses the CanvasRenderingContext2D interface can be updated in three ways:
- Draw an existing image on the canvas with drawImage() .
- Draw forms using different form drawing methods. For example, fillRect() fills a rectangle with some color.
- Construct an ImageData object and draw it on the canvas with putImageData() .
The first option did not suit us, because we did not have a board in the form of a finished image. There were options 2 and 3. The easiest way was to update individual tiles using fillRect() : when an update comes through the websocket, just draw a 1x1 rectangle at position (x, y). In general, the method worked, but was not very convenient for drawing initial state boards. The putImageData() method was much better: we could determine the color of each pixel in a single ImageData object and draw the entire canvas at once.
Drawing the initial state of the board
Using putImageData() requires defining the state of the board as a Uint8ClampedArray , where each value is an eight-bit unsigned number between 0 and 255. Each value represents some color channel (red, green, blue, alpha), and each pixel needs four element in the array. A 2x2 canvas requires a 16-byte array where the first four bytes represent the top left pixel of the canvas and the last four bytes represent the bottom right.
Here's how the canvas pixels are associated with their Uint8ClampedArray representations:
For the canvas of our project, we needed an array of four million bytes - 4 MB.
In the backend, the state of the board is stored as a four-bit bit field. Each color is represented by a number between 0 and 15, which allowed us to pack two pixels into each byte. To use this on a client device, you need to do three things:
- Pass binary data from our API to the client.
- Unpack data.
- Convert 4-bit colors to 32-bit.
To transfer binary data, we used the Fetch API in those browsers that support it. And in those that do not support, used XMLHttpRequest with responseType set to "arraybuffer" .
The binary data received from the API contains two pixels in each byte. The smallest TypedArray constructor we had allows us to work with binary data in the form of one-byte units. But they are awkward to use on client devices, so we unpacked the data to make it easier to work with. The process is simple: we iterate over the packed data, pull out the high and low bits, and then copy them into separate bytes in another array.
Finally, four-bit colors had to be converted to 32-bit.
The ImageData structure we needed to use putImageData() requires that final result was in the form of Uint8ClampedArray with bytes encoding color channels in RGBA order. This means that we had to do one more unpacking, splitting each color into component channel bytes and putting them in the correct index. It's not very convenient to do four writes per pixel. Fortunately, there was another option.
TypedArray objects are essentially array representations of ArrayBuffer. There is one caveat here: multiple TypedArray instances can read from and write to the same ArrayBuffer instance. Instead of writing four values in an eight-bit array, we can write one value to a 32-bit one! By using a Uint32Array to write, we were able to easily update tile colors by simply updating one index of the array. True, we had to save our color palette in byte-reversed-byte order (ABGR) so that the bytes automatically fall into the correct places when read using Uint8ClampedArray .
Handling updates received via websocket
The drawRect() method was well-suited for drawing pixel-by-pixel updates as they were received, but there was one weakness: Large chunks of updates coming at the same time could lead to stuttering in browsers. And we understood that updates to the state of the board can come very often, so the problem had to be solved somehow.
Instead of immediately re-rendering the canvas every time we receive an update via websocket, we decided to make it so that websocket updates that arrive at the same time can be bundled and rendered in bulk at once. To achieve this, two changes were made:
- Stop using drawRect() - we found convenient way update many pixels at a time with putImageData() .
- Transferring canvas rendering to the requestAnimationFrame loop.
By wrapping the render in the animation loop, we were able to immediately write websocket updates to the ArrayBuffer while deferring the actual rendering. All websocket updates arriving between frames (about 16 ms) were bundled and rendered at the same time. Thanks to the use of requestAnimationFrame , if the rendering took too long (longer than 16ms), it would only affect the refresh rate of the canvas (rather than degrade the performance of the entire browser).
Interaction with canvas
It is important to note that the canvas was needed in order to make it more convenient for users to interact with the system. The main interaction scenario is the placement of tiles on the canvas.
But rendering each pixel accurately at a 1:1 scale would be extremely difficult, and we would not avoid mistakes. So we needed zoom (big!). In addition, users needed to be able to easily navigate the canvas, as it was too large for most screens (especially when using zoom).
Zoom
Since users could place tiles once every five minutes, placement errors would be especially frustrating for them. It was necessary to implement a zoom of such a multiplicity that the tile would be large enough, and it could be easily placed in Right place. This was especially important on touchscreen devices.
We implemented a 40x zoom, that is, each tile had a size of 40x40. We wrapped the element