Leveling up Web Performance with HTTP/3
Join Lucas Pardue (QUIC Working Group Co-Chair and Cloudflare engineer) for a session on how HTTP/3 is supercharging web performance.
Episode 10
Transcript (Beta)
The web, the digital frontier. I tried to picture bundles of HTTP requests as they go through the Internet.
What do they look like? Progress bars, walls of text, block diagrams, anything you might be able to think of.
I kept dreaming of visualizations I thought I might never see, and then one day I decided to do Cloudflare TV and show you walls of text and progress bars.
Hello, everybody. Welcome to another episode of Leveling up Web Performance with HTTP3.
I'm Lucas Pardue.
I'm an engineer at Cloudflare. I work on things like HTTP3 and QUIC, and this is effectively what today's episode is about.
Some of the long-time viewers will be familiar with the kinds of topics that we talk about here.
In past weeks, I've had some guests.
Other times, I've delved deep into the protocol itself. Sometimes I talk about implementation issues, like how you can enable QUIC or HTTP3 or Firefox.
This week's a bit ad hoc.
It's very hot in the UK. There's a heat wave for anyone that is maybe here or just around the world if you're in the northern hemisphere because we're in the height of summer, so I apologize if I'm a bit tired from the sun and or just the general heat and lack of oxygen.
This might also affect the performance of what I want to do today on this machine that's getting very hot and is probably throttling itself, so we'll just see how today's episode goes.
If anyone was watching last week, you would remember that we were trying to compile the H2 load tool from Tatsuhiro Tsuchikawa's ng-http project, and we started off with compiling OpenSSL, and that didn't finish before the show completed.
I'll be pleased to let you know that it did eventually complete about 10 minutes ago, so we're in a good position.
We can then move on to the rest of the five other components that need building, so that might not be interesting to everyone, so I'm not going to start there this week.
I'll put that towards the latter half of the segment to let people zen out and kind of look away.
I want to talk about something more interesting now, which kind of came up this week since that program, which is around the concept of APIs, so this isn't necessarily anything to do with the protocol QUIC itself or HB3, but how we think about using QUIC from a programmer's perspective.
So a lot of you might be familiar with the BSD Sockets API that would allow you to open connection, send and receive messages, or to create streams for TCP and send into that stream, and it's a powerful paradigm that has got wide deployment in terms of an API service, but it has some problems too.
There's groups within around trying to design something better, and the interesting thing with QUIC is that it builds on top of UDP.
We have our layered sandwich kind of thing where HB3 is at the top.
We've got QUIC in the middle and UDP at the bottom, and depending on how much of a protocols purist you are, you might say that QUIC is a transport protocol, or it's not because it's running on top of UDP.
What we should have done is tried to deploy something like SCTP, which was a transport protocol.
It had the enumeration in the IP header. It was defined by the ITF.
It was a thing, and it needed to be used in a similar way in a kernel networking API perspective when you want to open a network connection or an association to somebody and send things.
If you compare SCTP, I'm not going to go into too much detail.
One, because I don't know enough about it, and I'll just look silly, but another because I've not actually used it, but at a very high level, it has a lot of similarities to QUIC.
It has the concept of streams, which QUIC has, and an ability to send information on them and to multiply, and that makes a difference to TCP or UDP where you're effectively feeding data into this funnel and it pops out the other side, and so the idea that you could do this with the existing APIs of the time wasn't quite right, so people came up with their own concepts of sending information onto streams, and so sometimes having a common API to do that really helps, because across operating systems, you can, say, build an application without having to come up with different code to handle the different look and feel of an API, or because you're interacting with the operating systems network stack, and so the difference with QUIC, as I see it, and I've experienced things, is that we have a lot of implementations that are written in different languages and targeting different platforms and operating systems, and we're all coming up with our own APIs that suit our kind of processing model, whether that's fallback-based or event -based, asynchronous or synchronous.
If you're working in Python, is it a Python native library, or is it, in our case with Quichem and Rust, are we looking at the Rust pure API, or are we looking at the C bindings onto that?
There's a lot of difference, you know, if you pick your axiom for how you want to operate, then you build an API that helps you work in that axiom, and there's not necessarily a good API for all of these things, and so the reason I'm talking about this is because the transport area director of the ITF, Paul Martin-Duke, who is very nice, sent an email to the QUIC ITF working group, which I'm trying to share.
This is part of a different thread of discussion, and all of these things in the ITF are all done on public mailing lists, so I encourage you to go and read up on this stuff if you're interested.
So if you're interested, of course you're interesting, otherwise you wouldn't be watching the show.
So yeah, here's Martin's email, and just saying it on the subject, speaking as an individual, I think it'd be useful just to find a QUIC application API.
SCTP did one, and the idea that an application would have to be written separately for each QUIC implementation is silly.
I don't disagree, that's a good phrase in these discussions, I don't disagree, but for instance, if we take an application like CURL, which we've talked about in the past, hello Daniel, if you're watching, we had Daniel Stenberg on the show as a guest.
CURL does implement its support for HTTP3 using two different QUIC stacks.
It uses NGTCP2 and Quiche, and the way it does that is to create effectively like a shim layer, or a wrapper layer.
That kind of works because you're talking HTTP semantics in CURL, and if that's your object model, it's great because you build to that abstraction of passing requests in and getting responses out, and the notion of a logical connection to a thing that you need to do.
If we're looking at other application protocols, maybe that doesn't exist, so if you're trying to do my bespoke protocol on the top, maybe you don't even need a layer in between, maybe you could just interface directly to the QUIC layer, and therefore it could be a bit annoying to have to say you want to use different C implementations of QUIC, and they all have different QUICs and different signatures or different approaches to how you create connection, manage the lifetime of it, send data on streams, whatever.
So I do see both sides of this argument, but if we look at the link that Martin shared here, which is the RFC for the stream control transmission protocol, this is a really pragmatic approach of modifying the Sockets API, as I've already mentioned, and kind of going through in a lot of detail about things.
So the abstract here describes the mapping to the Sockets API.
The benefit is that you'd kind of be able to easily and quickly port your applications from TCP into a CTP without too much overhead.
I don't know in practice how much that happened.
Yes, again, I don't know much about a CTP, but if I would envisage somebody trying to port an application from TCP to QUIC, looking at the lengths that HTTP had to go to to really map out what those things mean, yes, you could just open a QUIC connection and use one stream, but that's negating or ignoring the benefits of stream multiplexing.
Then you need to look at those things, and maybe it isn't just so easy.
Maybe it is. I don't know.
The point is I'd like people to kind of experiment with this, and I think the discussion is going to be ongoing, but for now it's kind of early days because of the different paradigms and approaches people want to take.
The extensions that we're talking about in QUIC are different ways to modify the congestion control algorithms or just experiment with things.
We're in the process of deploying this, not just Cloudflare, but as a community at QUIC people of what works, what doesn't work, and to maybe try and find a generalized uniform API for everyone I think would actually cause more issues for people than benefit.
The reason I say that is because I've got the table of contents up here and we're looking at like a hundred pages more of options and settings and having to conform to an API.
You can see here we've got deprecated features, so people taking approaches to things and finding that they didn't quite work out so well and wanting to take them back, but needing to continue support for them because it was already out there and people were using them.
That's always a problem with API. A lot of the software engineers need to use them and some of us might find them, so it's always a very tricky area, but it's a good discussion and I'm very keen to carry this on.
This is all talking a bit woolly and a bit abstract, so I thought what can I talk to on this?
I can talk to Quiche, which is an open source implementation and it's what we use to power Firefly's Edge.
I just thought I'd just take a canter to the Quiche API because we've talked about it in the past, we've never really looked at it, so this lives on many tabs.
Sorry for flicking, it's highly annoying, but this is an open source project on GitHub and it's a library or in Rust terminology what we might call this is a crate, so you can ignore GitHub and just search for Quiche as a crate and what that'll do is bring up this nice overview and if we were to go to this tab, you know, the Faulkner on GitHub banner on the top right and wait for that page to...
that's Faulkner and crates.io, whoops, shows how much I know her.
So, yes, where's the link to the code?
Yeah, it doesn't matter, but yeah, what we'd find if we went to the home page on GitHub, here we go, the repository, is that this page would look fairly similar.
So, we've got a recent commit by Juno, my colleague, to improve Reno's counting.
That's all right, congestion control. I'm hoping to get Juno on the program at some point in the future to talk about, not Reno, but all the cool stuff we're doing in this area, like Qubic, Highstart and other interesting things we're doing around flow control and congestion stuff, but anyway, yeah, here's the readme.
You can see it looks similar. There, real nginx module. They're reflective of each other, but we have like a getting started, which talks about the command lines, and this isn't an API issue at all, right?
Let's be clear. This is just how to use some example clients and servers that we've got.
That's basic connectivity to say this test site, which is web.tech, Alessandro's maintained endpoint that runs Quiche, and this would be a way that you could run the server too, because we get less handshake, and so we get into the API stuff, so this is kind of a very high-level documentation, either on crates.io or GitHub, but we have more in-depth documentation too in style of Rust doc, which is effectively documentation that's been generated out of code comments.
You're familiar with things like Java doc, whatever.
It's not rocket science, but the Rust docs can be generated offline and run offline pretty nicely, so yeah, this describes how to use our crate.
Some of it is similar to those other, but what we end up with is, you know, not end up with, what you start with is creating a connection, so this is quick.
There's a lot of parameters, a lot of things that make up the nature of a quick connection that, most of the time, you can just use defaults, to be honest, and things will run merrily, but you might want to tweak parameters like window sizes or what protocol version you're using, so what we have in Quiche is an idea of a config object, so this kind of contains all of those parameters that are important for the quick connection when we come to make it, but it can be, you know, the defaults are generally fine, so all of this does, if you're not familiar with Rust, you know, this is declaring a variable called config and then invoking the config object constructor with the version, and so this is just, you know, we're still developing quick, we're still taking it through iterations, although we're trying to slow down the frequency of iterations and stabilize and we're making progress on that, as I mentioned in the past, things like working quick pass call, establishing consensus and having a high barrier to changes, all of these things, so in the future, you know, we wouldn't be talking quick document draft versions here, which is how it is used today while we're doing the initial deployments, we're probably talking quick major versions, but that's something to think about for the future, but what this allows us to do is, say, try and create an older version of the quick protocol and the connection for that thing, because we're talking to a server that we know is, as you can see, this config object share between different connections, if we choose to make them, a client typically wouldn't, it would just make one connection to a place and process that, but you could do whatever you want, the server is going to listen effectively, and the way that we do these things is like you might be familiar with the Sockets API, we have a connect method and an accept method, so in this case, you know, let's do it back to front, the server would call accept and it would listen with what this field, the SCID is, it is a server's connection ID, so this, what we're passing in here is a reference to a field, a large byte block of stuff, which is selected by the server, there's lots of complexity there, I'm not going to bother going into, but effectively you can encode different kinds of information in your connection IDs, there's a bunch of connection IDs, and it's too complicated to talk about in this heat, maybe another day, but what they effectively, the point of connection IDs is to help route big packets, so both sides have an idea what other end they're talking to, and if you have stuff like load balances, we're able to coordinate and understand those connection IDs, they can make sure they get to the right place, even ignoring a load balancer, if you have software, say in this case, a server listening to different connections on the same UDP port, port 443, say, it's able to use connection IDs to dispatch the incoming packets, the right worker thread, for instance, or at least, you know, if you have statefulness happening, you're going to make sure that the packet, which has a session key, sorry, the connection has a session key, and the packet would use that to encrypt that, you're going to try and decrypt the packets with the correct one, and send stuff on the return path with the right thing, you know, if we go to the client, we're just connecting, we're connecting with the server name, this is going to be used for server name indication, so at the first phase, we're doing our TLS handshake, which we talked about last week, and those things go in, this is the client's source connection ID, and again, with a config object, so in this case, they're just both going to use the same config object, they're going to talk the correct version, or the same version, and everything will go along merrily, but on its own, that doesn't do anything, so in Quiche, what we were careful to do is separate out QUIC from the UDP layer, because some approaches would, when you call either connect or accept, would then call down underneath into a UDP layer, and call those similar functions, but by one downside of having a heavily integrated model is that it prevents you from experimenting with different kinds of UDP usage, or different transports, so QUIC is designed to be run over UDP, but you could imagine at some point, people might want to try different things, so the model that, not, sorry, not for these purposes, but the model that Quiche has, is that we would create a QUIC connection, and basically ask the Quiche QUIC connection object to prepare packets for us to send, or to accept data, and effectively process that, and return us things that happened, basically, and when we prepare data to send, we get back a buffer of QUIC packets, effectively, maybe even a single QUIC packet, that we can then call a UDP connections, send on.
The power for something like that is you can do very simple integration testing, because you can transfer those packets using just a pipe, or just put them into a memory buffer, and pretend on the other side that you're reading them from a UDP socket, when in fact you're just reading them as memory, it's a good simulation aspect, but yeah, the loops are different, so we're talking about handling incoming packets, we can see here, there's a very basic example in a never-ending loop, we have a socket, reading from a socket that has been created at some point somewhere, which I'll explain shortly, and in Rust parlance, what this means is read is going to be the number of bytes that were read from the socket, and we're going to try and receive into this immutable reference of a buffer, which is just a large space in memory to read stuff into, and call unwrap, which will basically handle the result.
So what goes wrong here, for instance, like a peer has terminated the UDP connection in some way that the network stack could detect, or some other kind of error, unwrapping here will instantly panic, which is probably not something you want to do in a production server, or client, but for the examples that we use, it's very useful, actually, because you immediately know there's been a critical failure, and typically, because this is a quick, sorry, a Rust system API, you know, standard sockets API, then we'll get some system information about, you know, what the operating system had issue with, with that UDP receive call, but assuming that that went okay, what we do is take that data that's in the buffer, and having been read out, and then we pass that into the receive function, which effectively reads as much information in this receive buffer as we know that we received, and tries to process the quick packets in there, taking everything that we've already set up with the connection ID order, and, you know, things might go wrong.
We might be done, and there might be no more work to do, because we've read all the data out of that buffer, there might have been no more data to receive at all from the UDP socket, the buffer at the UDP layer is empty, we can always break out of this layer, and conversely, on the send side, what we do is create some data that we want to send, and send it, which is brilliant, right?
And then there's complexities around needing to drive our quick connection, and timeouts, and different things like this, so quick is always running congestion control and loss recovery in the background, and things that need to be done, and work that needs to happen at the application, and this documentation describes that.
It doesn't necessarily help you build a working example, just from looking at the docs, but what's great is, you know, you can say, oh, well, I want to, you know, here's an example of sending some stream data, oh, what's this thing about can't establish, okay, well, that's explained later, after a handshake's completed, but I can dig into a more detailed explanation of the actual API here, or this is how you would send data on a stream in Quiche, right, the stream ID, and a buffer, and say whether it's finished or not, and this is how to use quick as a transport, and we have examples of us doing this, say, in the GitHub repository that we have.
This is the wrong kind of client to show you, but just in terms of a Rust native client, you know, we can take some of those API examples that we've seen, and we can just enter main, create a buffer for our inputs, buffer for outputs, got some argument parsing so that you can start this command line app and read things like, what's the server name I want to go to, what's the URL that I want to request, we have an event loop, we have an IP, how to actually connect the IP or UDP layer, blah, blah, set the stuff up, set the configuration object up, this one's important about verified peer, for simplicity's sake, just running a client, it can be connectivity issues initially, because people are using self-signed certs, for instance, which the client here would depend on the system trust store to verify, which causes problems, so you can set this to true or false, depending on what you're comfortable with.
In our more complex examples, this is a parameter, what you can pass in the flag, a bit like the insecure flag in curl, that you can specify explicitly, connect to this endpoint, and I know it's safe, but you can see here, we're saying other kinds of config, the idle timeouts, size of payloads, max data, and things to do with flow control, number of streams, lots of concurrency, we generate a random source connection ID, you might want to do something more clever there, then we call connect to the URL's domain, stuff happens, and eventually we go into this loop of polling and reading, and here's a bit more complicated, but you can see in total, about 270 lines of code with some boilerplate license at the top, we're able to create a pretty simple client that will read and write to a quick connection, and so that's our API, and it's not the only way to do it, and it will continue to evolve, because we're adding features and we're discovering things, different ways to do stuff, but yes, on the server side, can't type on this keyboard, it was a bit more complicated, because it needs to support multiple clients, so again, you don't need to really understand Rust for this, I'm just skimming over the details, but here we have a notion of a client object, well, that's way too complicated, but you don't need to know about that, we have a typedef for a hash map of clients, so we have some information vector of bytes, that's representative of the source connection ID, or a connection ID, I can't quite remember, and then, you know, some other information about the client, when we receive packets, we can take the connection ID, look up the correct client in this map, and make sure everything's going ahead, as needed, same kind of config setting, same kind of loops, or in foreign code, but it's interesting, because it can look long, but once you get familiar with this kind of thing, you start to see, separate the signal from the boilerplate, or the actually, you can add new kind of features and modes of operation quite quickly, the other thing that we provide in Quiche, is a layer on top, which is the H3 layer, the application mapping layer, and what this does is, to me, this is an interesting question, in terms of API, is what you're providing a general transport API, or an application API, how tightly coupled are those two layers with each other, so in Quiche, what we have is a H3 module, that handles all of the boring H3 stuff, it handles creating the control streams, and the QPAC streams, and validating a lot of the rules that were in place for H3, but not HTTP semantics, so for instance, if a server tries to send a kind of message to the client, that is unexpected, we'll handle the validation for that, make sure that the connection isn't completely broken, and when you want to send a request, we'll do things like allocate the stream ID, and make sure that it's all okay, so that you, as a user of the library, you don't need to kind of bog yourself down in that annoying detail, so the interesting thing, if we were to look at the connection setup, just like last time, we have this config, and we define a Quick Protocol Vision, we also call this Application Protocol Vision, so we provide a default one within the library itself, which matches the protocol vision, but you could put any string you want in here, what you're doing is using the Quick Transport to talk an application protocol, and you're just telling the other side what that is, and then the difference here is that we would create a HB3 configuration, so there's not actually that many parameters on there, but what it is, is a similar object to contain HB3 level settings, so there's again, not many of them, but you could configure things here, like the use of QPAC header compression, or the use of a push, these kinds of more optional aspects of the HTTP3 protocol, and then having created the quick connection, or the transport connection, we have a, in this module, a H3 connection that you create with the transport, so we're passing in, again, a mutable reference to that quick connection, from that point on, if this succeeds, then HB3 connection effectively takes ownership of many, it doesn't take ownership of the connection, but it takes ownership of driving that connection with application data, so in the previous thing, we saw that you could call stream send, and data immediately, but actually, when you're using HB3, the preference here is to use the H3 layer to send the request, so at this point, we've made sure that the H3 connection is properly configured, if you didn't do this, and you try to negotiate a H3 application protocol, and then just send a request manually, by sending some data, you would need to encode these headers as QPAC instructions, and create a headers frame, and put them, and you can certainly do that, there's nothing stopping anyone, but this is more of a convenience method, so in this example, what we have is a request, a request, which is in the form of a vector of headers, which is our own data type, which is just a tuple of string and string, so we have header name, and header value, effectively, method is get, the theme is HBS, authority, in this case, .tech, and this is kind of like the minimum request that you need to pass to people, you don't need a user agent, but it can be friendly in testing, especially if things go wrong, we can look in the logs and see what kind of user agent was causing problems for us, and that's just a request without anybody, you can see we have the true flag at the end here, which is to finish stream, that was set to false, you do need to signal, kind of finishing that stream, this is maybe a bit quick, it has confused some people in the past, but once you get some familiarity with it, it makes sense, you can either send a request without a body, in which case, it's immediately finished, or you can send a request with a body, in which case, you can send as many body bytes as you'd like, and then at some point, call fin, or pass true to finish this, that's what that example holds, and there's more, I could spend the rest of the hour talking through this stuff, but I'm not going to bore you to death, so, yeah, this is only one way to do it, there's other Rust libraries that implement QUIC and HB3, for instance, maybe there's others I'm not familiar with, and they might have some similarities and differences too, I think we're going to continue figuring out what works and maybe what doesn't, you know, the reason, maybe the motivation for each style of API that doesn't link into, you know, await and async kind of primitives that the experts in the room might be familiar with, is that we want to enable easy integration with the C findings, and the way that nginx works in our production kind of deployment, so, I've experimented with this in the past, you know, we found a few difficulties with some other libraries, and this is why Quiche has kind of driven the way it is to make our life very easy, to get this integrated into our C code base, so what we're looking at now is a file called quiche.h, which is basically manually generated bindings into Quiche's layer, so it can be called from C, so, you know, an enum here, arrows, boring, but the interesting stuff is creating a config object, yeah, very similar, you pass in a version, and get punted back to a Quiche object there, it basically follows a very similar pattern to the Rust documentation, which is why the documentation for this is pretty lightweight, but you can configure a config object, division, aggregation, you know what that is, you've got the key log, you've got Q log, which we've talked about in the past, there's all of that stuff going on, so the C layer is a very thin shim over the Rust, and that means that we're able to kind of fairly easily add features, and get them working without too much overhead, and head scratching about, you know, how do we convert this Rust paradigm into C?
That was, you know, one of the big challenges of marshalling data back and forth, if you're using, you know, more complex data types, so the things that we have, structures that are generally as simple as possible, here we've got bats, it was using T or UN64, things that are easily readable from C without too much additional support code, say, burden on calling application, and you can see this in the way that maybe you're trying to read headers out of, or stuff that's happening if you're off -operated server, I could probably spend another episode on that, but if you're interested, like, take a look, and if you've got questions, you know, be up now, because I want you to watch the rest of the program, but always feel free to reach out and let us know, the more people that use these things and ask stuff, the better, and on that note, let's move on to the next thing, so load testing, just a recap of last week, we're talking about ways to test servers, so we've talked a bit about just basic functionality testing, and I thought it might be interesting to look at the concept of load testing, but to get an idea of, you know, the performance of both the server or the client when there's some load on the server, so what I wanted to do was to build ng-gcp2, and all of the stuff, the first step was OpenSSL, but a special version of OpenSSL, because the way that QUIC works, we need certain APIs, you know, the theme of today, to get access to stuff that QUIC needs, and that isn't in OpenSSL by itself, so Tutsi Hero has a version that does that, just a patched thing, and last week, that's what we compiled up, so that completed, and going back to my banana slug work environment, if you go to OpenSSL, I believe it builds, so I would need to make install software.
Remember, the point of doing this is to find common pitfalls for people coming new to this, so this is a fairly fresh Ubuntu instance in Windows subsystem for Linux, so it has some dev tools, but not everything, and I always find it kind of fun to see what gets forgotten on that initial, you know, what dependencies did I have here, why did it break, just to show people that we all get affected by it, and then we fix it, move on, and forget, so when we see requests, don't be afraid to ask, because people don't know, or stuff changes, and somebody's reporting an issue that we've never seen before.
Quite often, the solution is to clean and make again, or check out, make sure you've got the most up-to-date version of code, but all that was doing was installing the built output of OpenSSL from last time, so we'd given it a prefix, that's when we used to build, and that was it.
So, with OpenSSL complete, we can go on to building ng-hb3.
ng-hb3 is Tatsuhiro's layer, application mapping layer, on top of ng-tcp2, so again, Tatsuhiro's gone for a layered approach to his code.
The quick layer is just that, and we're not bundling too much in, it's still a library and user space, but it could maybe depend on other stuff for the UDP network stack, so while this is building, or even downloading, part of the independence from UDP is that you can make better use of advancements, different ways to send messages, those we'll talk about while things are building, because chances are it's going to take a long time again.
So, we're going to the ng-hb3 directory, we have to run auto-reconf, which will just do some environmental detection and try and validate some stuff.
And you can see, yes, the first classic clangor, which is, I don't have that tooling to run that command, so we can just follow this very helpful message, just to install auto -conf.
This probably isn't the only thing that I'm missing.
What?
I'll spell.
There we go, time for a drink. So, let's talk about different ways of using UDP.
Alessandro did a nice blog post earlier in the year, I believe, about something called gso, or send msg, so this is on the Cloudflare blog, just looking at some of the technologies that are emerging from the Linux kernel, or the interaction with the network layer, to speed stuff up, and just some basic experimentation with this in an environment.
I can't go into this now, but basically our baseline is using the send message function, or system call, to send a single UDP packet, and you can take alternate approaches to this, and you have this send m message, which can be used to send stuff in a different way.
I swear it's an artifact of this, to show that even just installing stuff is slow here.
So, I'll try reconfigure. Yeah, check out this blog, it's got some nice examples of stuff, and some charts to show improvements.
We're not the only people looking at this. Go and look into other stuff.
This is a constantly evolving area. One of the discussions around QUIC is whether it would be better in the kernel or in user space.
I think the jury is still out on this.
Having stuff in the kernel doesn't necessarily instantly get you performance wins.
It can do by reducing this call and context and all of this stuff, but it doesn't necessarily create a QUIC win.
So we need lib tool. I'm sure a bunch of other things.
So if this will work, and meanwhile, I'm going to try and find a list of dependencies that Tatsuhiro has somewhere that lists all of these things.
And quite quickly, having gone this path of doing once you realize, chances are your system already has some of them, so you don't have all of this pain.
If I was to go to a, I recall from my days of building ngHB2 a lot, that does have a single one-liner, fun2, apt install, here's everything you need.
It lists them out, but no one remembers all of the packages that are needed.
So, yeah, here we go.
On ngHB2, GitHub repo. Do all of that and see.
36 megabytes.
That will probably work.
Presume I'm still connected to the Internet. Why am I not connected? Python, Python, Python.
Remove Python. Hands up, anyone who's tried to remove Python?
Not found here.
D. I presume those instructions are maybe a bit old. I think the libSSL one can go out.
This is the problem when you copy-paste code. It doesn't work either.
I would have thought libSSL dev out of here, too. To be honest, I think the way I'm going to build stuff won't need all of these anyway.
I've just got an image with extra packages.
I'll probably want to go back through and delete stuff I'm not using.
But this is just a throwaway VM, really. I don't plan to use this for anything else other than these live TV segments.
That's making some progress.
Back to our build instruction. Assuming we'll be able to alter read conf, we can then do read conf here.
Again, we're going to use a prefix. We don't want this to go into the system.
If you recall from last week, if you saw or not, we installed the system H2 load tool, which will work fine for testing HB 1.1 and 2, but it doesn't have HB 3 support.
That's kind of our motivation for building from scratch here.
There we go.
Fingers crossed. Anyone who saw last week's show would have heard about the decorator that I had.
It was like watching paint dry.
That paint has dried now, and the decorator is done. You can't see my nice new background because I have this exciting video I'm constantly playing, but it's a nice shade of gray.
I believe it's polished pebble, although we're in the know.
Anyway, we're going to run configure here. Lib only. We don't care about the NGHB 3 test apps, I guess, if there are any.
What we're trying to do here is build a HB 3 mapping library layer that will integrate with both NGT 2 and NGHB 2.
The fact I can remember all of those things is pretty sad. Ideally, you never need to worry about this stuff, but the idea is that effectively when we tell H2 load to use H3, it won't complain.
It doesn't know what that is anymore. It will auto known because it's got some additional compilation and whatnot.
I'm actually watching this because stuff does go wrong here, and I've had to, yeah, basically, tattoo hero's output is quite useful to see what's missing.
Maybe not so much for NGHB 3, but for each H2's equivalent, there's a lot of configurability about generating Python binding.
Other stuff that can make you believe that you miss independency, so you need to do other things when actually you don't.
Once that's done, yes, we can run make again.
Again, because I'm trying to stream at the same time and host this whole thing, I don't want to use all of my processes because I really think that will upset the machine, so I'm going to do this single threaded and probably end up in exactly the same situation as last week where the build didn't complete by the end of the show.
I'd like to give people a cliffhanger ending, so just imagine me like one of those people in 24, if you ever used to watch that TV show, the tech person has to do stuff and no one really understands what they're doing, and then they end up dead.
I hope that doesn't happen to me. But we can see here there's some QPAC work.
I probably want to dig into that a bit more at some point in the future, talk about head of compression and how it differs from HB3 and HB2.
I don't know if anyone's interested in that.
If you are, let me know. You can email us at livestudio at Cloudflare.tv or tweet me.
I was having a nice interaction with a chat today who was comparing QUIC to SEP and SSH.
He's provided a gist to compare his test setup in AWS EC2 client and server and comparing it to just doing a transfer by SEP.
You're seeing some performance characteristics and I want to see maybe why is Jeff seeing what he's seeing.
Maybe there's something funny going on in the environment or sometimes there's bugs or performance issues that are triggered by certain network parameters.
So, yeah, thanks for that, Jeff, and I look forward to following up on that.
Anyway, we can make install Pandavi Dosey.
And then we're going to go on to our next clone from here.
So, this is the big one, NGHB2.
I suspect that'll take us a lot longer to build than NGHB3. How do I type that out?
Annotations. That was quick.
Oh, brilliant. What a recon trick again. I can't spell again. If anyone's used a Microsoft Surface keyboard or the flip lid thing, it's a pretty decent keyboard.
It's just it's a different layout than my American one on my work laptop.
So, I'm switching back and forth constantly, even during the segment. And also, I just can't type very well.
And some text.
My aim is to kick off the compilation and then we'll take it from there and see.
I doubt we're going to get done with this by today, but we've made progress.
We've done one thing.
So, at this rate, if we look at the future instructions, we do need to build NGHB2 with those flags, and that's not listed here because we're looking at the NGTCP to build instructions.
So, yeah, that'll probably give me the time to remember them, figure it out.
Yes, I'm not a Mac user. I know many of you are, so I don't have to do this horrible thing that I don't understand because I've never used Mac ports.
But let's run the configure line here, which has to do some jumping through hoops for package config, which I dislike.
I think even we have to do the same for NGHB2 when we get there.
What happened last week?
Cody has given me my five-minute warning.
Cody is one of our support crew, like many of our support crew.
He helps us out in the background, fielding questions and passing them on and making sure the stream is set up before we join.
So, I just use it as an opportunity to speak to people because I'm still at home in lockdown.
This is basically the old excuse of the show, so just get some face-to-face time with people.
Remember, if you're lonely, reach out to people. Yes, C++17.
I was a big fan of modern C++. I remember when I was getting started with NGHB2, I was on a CentOS system that didn't have an up-to-date C++ compiler on it, and that caused me all kinds of fun, having to build stuff in a very specific way to get things done.
It was not fun. I'm very glad that these days I can play with Ubuntu.
I don't need to worry about such problems. The C++ stuff is cool.
Back in the day, I remember having to use like threading libraries, not even Boost, but bespoke POSIX.
You've got pthreads, but it wasn't even that.
It was some other library, and all of the fun of that. Here's all the useful output I was trying to talk about earlier.
Yes, we've just got our compiler flags and string stuff, but you've got the idea of C++.
Yes, well, I don't really. We've got two different kinds of libraries.
Yeah, JMalloc.
That's very important for nothing in what I'm trying to show you, but it's cool.
Yeah, there's good configurability here. What's going on with GNU TLS?
I'll have to ask Tatsu here some more about this. Anyway, two minutes to go.
What's the next step? Can you guess? Yes, it's make, so single-threaded make and the check.
Make sure that it's built okay and not completely broken, which I have had in the past by weird environmental configuration.
Yes, I have about one minute left.
If this topic or these topics have not enthralled you, please let me know, or if they have, please let me know.
There's lots of different angles to talk around transport protocols, not just QUIC or HP3.
They're a part of the story in how we look at web performance at the network layer, and there are directions with all the HP versions, little stuff like looking around push and prioritization or congestion control.
There's a lot of different topics we can look at here, as well as practical tips of actually throwing the theory away and validating things in practice because there's so many parameters, so many different ways of testing stuff.
The more coverage we get, the better. Oh, well, if this show is still on for some reason, goodbye for now, and I look forward to seeing you all next week.