How are you?
I think the bird gave this, is a thing serious?
Why are we saying that?
I know.
But maybe the thing is u who's are knowing one thing
why?
I know you know twerry
meaning anything
You know how a girl tastes?
Is this a human?
I know I know
I'm sure.
Hi.
Yeah.
No?
Where are you going?
You don't?
Yeah.
That's me.
It's very, very delayed.
I can hear people talking about burgy beds.
The feature of iceberg.
He didn't know that was coming.
I thought he was whispering that.
So nobody could hear me.
But I'm Mike right here.
Yeah.
Oh, it is 10.
30.
No.
That's such a way to feed.
That's really annoying.
No.
I'm welcome to these things.
I have an experience.
We've got a great night today.
So we're a little bit behind.
If you talk first.
Speakers.
Which is what is the spec.
So the deco and Russell.
Sorry, sir.
Quick.
That's all we need.
We can introduce ourselves.
In fact, we plan to do just that.
So I guess we can take it away.
Who are these people?
Presdest.
You probably shouldn't actually, but maybe by the end you will because we're going
to have too much fun here.
So to kick it off.
I am Danica
No, Louder.
I'm going to leave some ears.
Okay.
My name is Danica Fine.
I'm a developer advocate at Snoof Lake focusing specifically on open
source.
I absolutely fry blue police bullets.
So in the past, I kind of finished it.
We can do some practices for the public, and excuse me.
with this gentleman here.
Hi everyone, so I'm Russell Spitzer.
I am one of the Apache iceberg PMC members.
I'm also a member of the Apache Polaris
incubating PPMC.
You might know me from a lot of my previous work
and a lot of open source things.
I've done a lot of stuff with Cassandra and Spark
and all that.
And I also currently work at Snowflake.
And I'm excited to talk to you all about open source software
today.
So as you may know, we're going to be talking
about the latest features that are being added
to the V3 spec of Apache iceberg.
But before we do that, we want to kind of set the stage,
make sure that we're all on the same page here
and what we're actually talking about.
So Russell, what even is Apache iceberg?
Yeah, well, so a lot of you might think
that Apache iceberg is just this library
and a bunch of code associated with it.
But what I'd like to say first is that primarily
Apache iceberg is a specification.
It's a set of rules about how various libraries
or engines would interact with the set of data.
This is a really important distinction
because this is one of the key factors
that makes Apache iceberg such an interoperable system.
We basically said, these are the rules first
and then we have the code next.
So for example, we might have a bunch of rules
that are like when we add a new data file,
what you do is you add it to something called a manifest.
And then that's the important part.
Then all the engines go out
and they do their own implementation
of how exactly they want that to happen.
But because we have the rules all set out the exact same way,
Trino, Spark, Python, whatever language
you're using to interact with your iceberg table
knows exactly what they have to do to be compatible
with every other engine that's working with iceberg tables.
So there's more than one specification
within the Apache iceberg product.
And that's one of the quickly go over a couple examples.
So the one that we're going to be talking about today
is basically the table format specification.
This is the key specification, which is literally,
this is how you're going to lay out files.
This is how you transition from one state to the next.
But we have a whole bunch of other great specs
that we've written up.
There's one on views, one for interacting as a catalog system.
We're not going to talk too much about the catalog
interactions today.
And then one called Puffin, which is aboutcillary statistics
and other blogs, we might want to have associated
with our iceberg tables.
And then of course, along with this,
the Apache iceberg project has a bunch of Apache projects
or subprojects for different implementations
of these specifications.
The one that you'll find at the Apache slash iceberg
repository is our Java implementation.
But we also are strongly in support of implementations
like Pious, Bergen, iceberg rust.
And then of course, we have organizations that are not
part of the Apache Foundation that are also
doing implementations of the iceberg spec.
And Trino is one of the examples of a product
that's doing that.
Rice.
So that's kind of the high level overview
of what iceberg is really by definition.
But how many of you are currently
working with iceberg right now?
All right.
So this next bit is for the rest of you.
So that we all know we're talking about through the rest
of the talk.
So in order to actually look a little more forward
at what the new features are, we need to set the stage
with some actual definitions.
And by that, I need to look at the iceberg architecture.
Because we're going to be talking about a lot
using a lot of these definitions as we move on.
So we saw what I suppose.
We have our set of rules.
And it tells us how we're actually interacting
and building out these files.
So when you want to write data to an iceberg table,
what we're really doing is we're writing data
to some data files.
Wonderful.
These are by default format in Part K.
And then from there, iceberg has a wonderful layer
of metadata that helps to coordinate those data files
into iceberg tables.
So we have our data files.
And those data files are then tracked by manifests.
So the manifests may point to a number of data files.
But an important thing about these is that they
will track a subset of data files so that when we actually
want to query the data and those data files later on,
we can prune off irrelevant data files.
So we'll keep track of some statistics
on the actual data that these files then point to.
From there, we're going to roll those manifest files up
into a manifest list, which is exactly what it sounds like.
We have a list of manifest files in that manifest list.
And then those statistics that we're in the manifest files
are also rolled up into our manifest list.
And an important thing to note here
is that the manifest list itself is actually
just a effectively a snapshot of what that table currently
looks like.
So all the files that are relevant to that table at this point
time are listed out in that manifest list.
But we're not done here.
You think we need to be done.
But there are more metadata files, specifically
a metadata file.
We've got real clever with that naming here.
So we have a metadata file to track the individual snapshots
that exist within that table.
So here we have snapshot 0 that is maintained
by that manifest list at a point, too.
But we also include some additional non-data information
in here, like the schema, how we're actually
partitioning our data as well.
Everything else that we need to sort of round out
how we're organizing our data.
But we don't stop there, OK?
There's more, but wait.
So we need two really important things.
One, we need a way to link a table identifier
to this metadata file that we have here.
And then we also need a way to transactionally indicate
that this metadata is the metadata that we care about right now.
And if we want to update that, we need
to be able to automatically do that.
And so these two things are handled by an external bit,
called a catalog.
So this is what we're going to keep that mapping
of our actual tables to the relevant metadata files
that we care about.
So if you want to add more data to your iceberg table,
which probably you would want to do, if you're
adding that you're working with iceberg.
So say we want to add some more data files.
Great, you are free to do that.
But naturally, we need to track that with a new manifest file.
And then we're going to roll that up and create a new manifest
list.
But since we're adding data to an existing table,
we're not just going to point to that manifest file.
We're going to point to all of the manifest files
that are relevant to that table now.
And then from there, we'll create a new metadata file.
Now we have a new snapshot because we've added a new data
to this table.
We have a new valid state of this table.
And so now we have snapshot 0 that we're still
making track of, but we have the newest one snapshot 1.
And then, of course, at the very end, we
want to be able to update the pointer in our catalog
to this new metadata file so that we know what we're looking at.
OK?
That's your crash course and all things iceberg.
You're good now.
We can take it away.
Cool.
So all the stuff that Danica just went over
is part of what's in the format specification.
And that's a lot of what we're going to talk about today.
And just to give you a quick history
of where we've been before and where we're going to,
I'm going to talk about how the spec is basically evolved.
So we started out with V1 of the table format spec.
This is iceberged about circa five years ago.
And it was just the initial description of that system
of manifest, manifest list, data files,
as well as the rules about how we're going to put metrics
at these various levels and how we're going to do
partitioning.
So with this, you had everything you needed to do
to build a basic table.
But one of the things that we realized really quickly,
but we were working with this version of the table,
is that we have a lot of difficulty doing
row-level operations, which is extremely common concern.
Because we want to be GDPR compliant.
We want to be basically very good to our users with privacy
and getting rid of rows that they don't want
to exist in our data sets.
But that's traditionally very difficult
if we have this layout where every single data file basically
is either in the data set or not in the data set.
So it makes it very difficult to do tiny updates.
So V2 of the iceberg table spec introduced a lot of things
to help out with row-level operations,
specifically the concept of delete files.
We're going to talk a lot more about delete files later,
because it's one of the big changes in V3s,
how we deal with those.
But we introduced basically two ideas,
a quality deletes and position deletes,
which allowed you to have a secondary set of files
that just indicate a delta to your existing files.
So you can imagine you would have a file
with your actual rows, and then another file that says,
actually, if you're at this point in time, ignore rows 5, 6, and 7,
or something like that.
Like I said, we'll go into more details about that later.
And then today, you're here to hear about V3,
which is the changes we're going to do over V2
to hopefully get even more use cases into iceberg,
improve the way we handle deletes.
And of course, move us forward to an even more efficient data format.
Tell us what else is in V3 Russell?
So V3 has a lot of things.
Let's go over the headlining features.
One thing to note before we go through this,
V3 is going to be an upgrade process.
So if you have a V2 table, you will have to upgrade your metadata.
If you would like it to be a V3 table,
this should be very lightweight, because it's only
at the metadata layer, not at those data layers.
But we have a couple of great things we're about to introduce.
Like I mentioned, we're about to move into delete vectors
from position delete files.
We'll talk about the details of why this is important later.
But basically, we're moving to a more efficient representation
of our deletes.
We're also going to introduce something called a variant type.
If you're familiar with some data warehousing solutions,
they already have this idea of a semi-structured data type
that lives within your structured table.
The really cool thing about variant is that suddenly,
you can have this kind of varying structure from row to row,
while still having structure within those rows.
We'll talk about that implementation later as well.
We're also adding a geometric type.
This is a common concern for a lot of folks working with data,
where we have parquet files, we're
assoring our geometric representations,
and all sorts of different ways, which makes them
not very interoperable.
And the key message of iceberg, right?
It's we want to have those rules set out
that say how every engine will deal with this data,
so that we can have them all working in the same way.
So we're making sure that geometric type
is represented inside of the iceberg spec.
We're also implementing features for row lineage.
We've found that a lot of folks really
want to be able to say when particular rows were added to their data set,
or when they were updated.
So we're adding that again as a first class feature
into the specification, so that all the engines can
work with row lineage in the exact same way.
And finally, on this list, we have default values.
Previously, we had this system where if you wanted
to have a field be optional, your only option
was to have no, as your value of people are not submitting a value.
We realize that there's a lot of use cases
where people do want to have a value in that column,
even if they haven't actually send something in with the writer.
So we're going to have that as part of the spec as well.
This ends up being a little bit more complicated
and I'll go into reasons why when we get to that part of the talk.
But for now, we're going to go on a journey
from the beginning of deletions.
So I know I took you on an overview of the iceberg architecture,
keep all of that in your mind, because it's going to be relevant here.
So deletes, that's a simple thing.
We all know how to delete things.
There's actually a little bit more to it in iceberg.
So there's two ways to actually think about deletions in iceberg.
But as Russell mentioned, we're going to add in this concept
of a delete file of a way to say, these are the rows
that we don't really care about in the data set that we currently have.
So the first way that you would think about our deletions
is with copy on right, and then we also have merge on read.
So in copy on right mode, if we have three rows,
in our iceberg table, A, B, and C, and say we want our delete row C.
It's no longer relevant to us.
Well, then we're just going to copy on right.
When we decide that we want to remove that row,
we are going to rewrite the entire file without that row C.
If that sounds horribly inefficient, it does.
Yes, you're probably right.
It can be horribly inefficient.
But that's an option for you.
With merge on read, we actually have two mechanisms for handling our deletes,
depending on what type of deletes you want to use.
So but with merge on read, the construct here is that we are adding the idea of a delete file
to tell us which rows are no longer relevant to us as of a current timestamp.
So with the first type of deletes on merge on read, we have an equality delete.
So with equality deletes, this delete file can apply to multiple data files.
And it says whenever we see a row from a timestamp earlier,
then in this case T1, and it has this value C in it,
and we are going to ignore it.
We wanted to remove that from our data set, ignore it.
And when do we ignore it?
On read, hence merge on read.
The other way, otherwise, we could be using what is called a position delete file.
And so this file could also apply to multiple files,
but you'll see that it gets a little more specific on determining which row we're going to ignore.
So here we're storing a tuple that says, for a given data file, file A,
we are going to ignore the third row.
So it identifies that specific row by its index.
So position delete files might seem simple,
but they've got some fun things going on under the hood.
So, namely, you can choose the granularity
at which these position delete files actually apply to your data set.
So if these are our delete files here,
we can say that all these data files belong to one single partition.
So then we can choose to have delete files apply at the partition level.
So for this entire partition, all the data files in there,
we are going to have our delete files applied to that partition.
So all the deletes across those files are going to be stored in one delete file.
Or, we can decide to have the granularity be at the file level.
So for each individual data file, we are going to store a corresponding delete file
that then tells us to ignore rows in that specific file.
And so there are trade-offs here, obviously.
So in iceberg, we have an ongoing problem called the small files problem,
which we like well-named things in iceberg.
So generally, we don't want small files,
because when you're actually running queries on your iceberg tables,
file IO is going to be the biggest killer of performance.
So where we can reduce the number of files that we are opening over time,
we want to be doing that.
So obviously here at the partition granularity,
we get the benefit of only having the open one file.
And for reading from all the files in that partition, that's great.
We only have to open one delete file.
But if you're only reading from one of those files,
and you have to open the delete file and scan through it,
well, then that sounds inefficient.
So then you might want to use the file-based granularity,
because then if you're reading just from a single data file,
you only have to read that one corresponding delete file.
But the trade-off there is that we have more small files,
more of those small delete files that we have to deal with.
So we're constantly battling between these two things.
We want to find an optimal way to deal with them.
And so this is where deletion vectors comes in.
So with the addition of deletion vectors in the V3 spec,
we're going to replace this idea of the position delete files
with a new type of file, the puff and file.
And we already kind of mentioned the puff and file.
This is actually a thing.
It's a door-ably named because they live on icebergs.
That's the joke.
It makes sense, okay, when you think about it.
So puff and files are already used in iceberg.
They're a very efficient lightweight file.
And we use them to store statistics elsewhere in iceberg.
And so we're repurposing them here to store our deletion vectors.
So in this case, a single puff and file can store many data files worth
of deletion vectors.
And we're going to store those vectors at specific offsets
within the puff and file so that when we need to come and read
those deletion vectors for a specific data file,
we don't have to read through the entire puff and file.
We can access that by its offset within the file, okay.
So we kind of get the best of both worlds here.
We can store our data in one file so we get the benefit
of a bigger file of deletion vectors.
But we can still, we're only opening one file then
and we can access that data specifically within the puff and file.
And then also often our metadata, we're going to say which offset
that deletion vector exists at within the puff and file, okay.
So this brings a lot of benefits.
It means that for the small files problem,
we don't have to worry about compacting our data later on,
reducing the small files that we have
and compressing them into a bigger file.
We're going to have larger files and we're still going to be
more performance because we're reducing that file
I-O and we can access exactly what data we need in that puff and file, okay.
So this is effectively what it'll look like here.
It's pretty lightweight for showing for the data files.
They are going to have their deletion vectors
specifically at an offset in the puff and file.
We know exactly where that mapping is.
So what do you get out of this?
First of all, you don't have to do anything to make use of this.
That's wonderful.
No user code changes necessary.
And you get a lot of benefits here based on performance.
Your query planning is going to be better.
And maintenance tasks like compaction.
You don't have to worry about that as much.
So there's a lot of benefits to actually moving to this construct
over the position files.
All right, Russell.
Very intives.
What are we dealing with those?
So we just talked a lot about planning.
Now we're going to talk about core data types that we're adding into the system.
So variant type is one of the most exciting things we're adding into iceberg.
And it's really great for a lot of different scenarios.
I'm sure everyone here has dealt with some kind of IoT or streaming instance
where the folks that are giving you data will not tell you what the scheme is.
Or they'll want to change it every few months.
Or maybe they're giving you elements from a whole bunch of different sensors
that have different formats.
This is the time when you probably would like to have something that
has a variable schema itself with inside of your table format.
So you can handle that appropriately.
We're also dealing with a lot of places where maybe schema changes over time.
And although we have a couple common fields, we have a few that just kind of come
and go depending on which application version is publishing events.
And finally, if anyone is actually using a map type,
this is way more efficient.
So commonly, I see people using maps as basically a string mapping
to some other type, but within a normal type database,
that means you get one type for your value and you have one type for your key.
And that ends up being very painful.
All right, so let's go quickly through how this is actually implemented.
So the way that a variant is actually going to look
within an iceberg table is primarily stored as two different fields
that belong to the same struct.
You can imagine this with a metadata and value portion.
The metadata portion is going to explain to us exactly
what is the schema of this particular rows value portion?
So I might have a particular row that has a x, which is an int,
y, which is a long error, which is a string.
We take that, we have a binary representation,
which is then specified inside the variant spec.
And then we have a value portion, which then has the actual values
for this schema laid out according to the metadata.
So this is all really good, but you're probably looking at this and saying,
that sounds like I'm going to be disserializing and serializing things
all of the time, and how in the world am I going to get any metrics
that help me scan through this?
I can't prune based on metadata blob looks like this,
because that's just a bunch of binary.
My minmax values mean nothing.
So there's one step forward past this.
This is something that's implemented in a lot of commercial data lake houses
and commercial data warehouses, and it's called
Shredding or Subcounterization.
And basically, the idea is, what if I combine this idea of a unstructured type
with some actual typed values for the things that appear very frequently?
Now, the actual method for determining which are those common fields
and exercise that we're kind of leaving outside of the spec,
but what we will be saying is that we are going to accept a variant type,
which basically has an unstructured portion of the metadata and the value,
and then a structured portion, where if for this particular row,
we have the presence of one of these columns in a known type.
We'll store that as an actual typed column in parquet.
So suddenly, I now have an actual column with minmax values
that I can use for pruning.
So in this example, say we always see x.
We always know that x is an integer and it appears all of the time in our data
that's coming into this variant field.
Basically, what we can make is an x typed sub column
that exists within the same struct,
and inside of the variant spec works describing,
if you see that, you know that that's actually part of the same value,
and if you see only values in that column, you can do pruning on it.
So basically, a smart engine can take data that's been written with a lot of common columns
and extract them out into typed columns that can be used for pruning.
In this example, I'm showing what if y has been typed in this particular instance
to be a Boolean, we can actually have it.
So sometimes we say that it's typed, but if we don't fit that typing,
we can still have a different value inside of the untyped portion.
So engines can kind of be smart about whether or not they're going to use that information.
So for example, if you're doing a query that says,
I'm looking for when y is true, you know that you don't actually care if there's a y value in here,
because if it's Boolean, it's going to be out there.
And if it got stored as some other type of value, we know we don't care about it,
and we can ignore pruning on that point.
Anyway, so that's what's really cool about some structure data in various types.
If you want to use this type in V3, you're going to actually have to change your application code,
because you actually have to start writing a variant object, which will end up being different,
depending on what engine you use, but it will be encoded.
Like I said, if you're using shredding, there'll be no performance difference between this
and using real typed columns for the values that are untreaded.
Those that stay inside that serialized blob, you do data serialization penalty any time you try to read them.
Let's move on, because I think we're running a little low on time to geometric type.
If you thought we were talking fast before, we're going to speak even faster.
Okay, so geotypes, you might need a little bit of motivation here.
Actually, no, I probably don't need to convince a cell the room on the fact that geotypes
are useful. I'm not saying you should just come to peer pressure here, but geotypes are out there
and they're here to stay. Sorry, there's already been widespread adoption of geotypes and
number of projects within the iceberg ecosystem, like Dr. D. B. Hive, Flank, Spark, the list goes on.
So it makes sense to support geotypes at iceberg as well. So we have succumbed to peer pressure,
I guess you could say. So more practically adding support for the geotypes at iceberg would unlock
a lot of benefits here. And namely, we're not just trying to store the geotype,
although that is an important part of this, but we want to be able to interact with those geotypes.
So enabling additional, like being able to see if certain geometries are within other geometries,
using other predicates, and then even going so far as to actually partition the data based
on the geometry, the geotypes as well. So this is what we're adding as part of the V3 spec.
So how do we make that happen in iceberg? Well, it starts by formalizing what we're actually
talking about. So we have geometries, like flap services, and then we also have geography,
which are more interesting services. So being able to support both of those types.
And then the key thing to know about geotypes in iceberg is that it's a hard problem to sell for,
okay? So going back to the column-wise metrics that we want to keep track of so that we can actually
do effective pruning when we're querying data later on. We need to be able to capture that
within the geotype. So to support the pruning of a relevant data in iceberg, we actually
capture those metrics as sort of a bounding box around the geometries, and that's how we determine,
you know, what that sort of maximum in the range of data that is in that partition is.
And the one important thing to note here is that we're not just making this up on how we're dealing
with an iceberg. We're aligning with the implementation in parquet. Are there file formats?
Who's to say? We'll get there at some point, but it is aligned with parquet.
If you're interested in other file formats, I encourage you to get involved in the iceberg project,
because we currently do not have a lot of maintainers who are focused on anything other than parquet.
This is a job offer, folks, for open source. So what does this all mean for you?
Well, to use geotypes, obviously, you're going to have to use geotypes first and foremost,
so there are some intervention on your part. Only new columns are going to be able to use
geotypes. You can't do promotion of old columns to keep that in mind. And there will be some,
you know, hands-on effort with involving some user code here to take advantage of it. But the cool thing
is that you get some stuff for free here. You'll get that pruning automatically and predicate
pushed down. So there's some benefits of it. Role lineage.
All right. Role lineage is basically the idea that for every single row we want to know exactly
when it was made in the history of the table and when it was last modified. We're going to
accomplish this for a couple different reasons. The main things that we want out of this are the
ability to do better CDC and basically get delta's out of table between different time points.
So for that, we basically, like I said, need these kind of identifiers for when a row was made
and when it was last changed. The way we're actually implementing this is to through,
we have an R there that shouldn't be there. It's just row ID and last updated sequence number.
These are two fields that will now be required by the spec if you are deciding to enable
Role lineage, which is basically then a contract that we've described for all of the engines to follow
to properly fill in these values. Now, we're trying to do this in the lightest way possible. So,
basically, when nothing has changed for any of your rows, there's no change to the data files,
which is we're going to keep the information required to populate these fields at the metadata layer.
Now, once you get through a point and those things have changed, you're actually updating
rows. That's when the engines actually have to start taking account of what these values are
and persisting them. So for a penders and people who are using this for the first time, no penalty.
The moment you start doing updates, we start having an additional cost in terms of storage and
engines need to do a little bit more work. So I'm going through this fast, but we're running a little
low on time. If you're using this as an end user, like I said, it's something that must be enabled
within your table. It's in the new field for the spec. So even if you upgrade to V3, you do then
have to turn it on. And once you do that, all the writers that you're using must be able to respect
these rules. So like I said, this is something that we're kind of making an optional feature
within V3. So you might have certain engines that will support it and won't support it, or might
support it points in the future. And that's really all I want to say about that. Let's go on to default
values in our home strip. The fastest overview of default values ever. So default values might seem
like an easy problem to solve for, but it's actually not. You should have new perspective words.
But we are actually bringing, I think, and the worst mistake in Peter Science 2 iceberg are actually,
well, Nulls were already in there, so we're kind of solving for it at this point by adding
the default values. Why bother? Well, Nulls aren't that great. You know, we can throw them in there,
but they might break things later on for our readers. So to get around it, I'm sure we've all
implemented in some way or form default values for ourselves. But they're also valuable in being
able to specify common values as well. So in iceberg, as we said, Nulls can already exist,
but default values are going to affect how we deal with those Nulls and actually when we deal with
those Nulls, either at read time where we replace the existing Nulls or at right time where we
avoid storing the Null are all together. So how can you use default values in iceberg? Well, with
some effort. So we're not going to hold your hands, but we're going to try to make it as easy as possible.
So with those two things to consider, as I mentioned, at read time or at right time, we can handle
our default values, our Nulls. We are adding two additional table parameters to help you manage those.
So with initial default, we're looking at the Nulls that already exist in our table already.
And with this parameter, we're saying that when we actually read those data later on,
we're going to replace those Nulls with the default value that we specify. And it might not sound
like it at first, but this is sort of your one and done. You're reading this data and you're
replacing that Nulls. So we also read data when we compact our files later on to avoid the small
files. So when this data is rewritten, you were done. That Null does not exist anymore. You cannot
go back and change this. That's why it's sort of a final. We also have right default. So here
we're avoiding adding those Nulls and replacing it with a default value to begin with. And you
can change this whenever. And as a bonus, you can use these instructions. Cool. What's coming next,
Russell? Cool. So obviously, we're not done with the format. Even though V3 has not come out yet,
we're already starting to think about all the things that are coming in V4. So just as a quick
send away, join the Apache iceberg community and come and be involved in all the work we're doing
for V4. We want you to be involved. The iceberg summit for 2025 is about to come up in our
call for papers as open now. If you have anything you want to say about iceberg. And of course,
visit us at Slack in zero, all kinds of fun places. And if you'd like to work at snowflake
like us, we are also hiring lots of open source books. So thank you very much for your time.
Thank you.
We did it. If you don't mind, there are a lot of people that want to answer. So we are going to be
at seeking his way. So people can answer that way. If you're really important, let's go.
Do you like how to live? What's mine? Do you want to live in?
I don't think you're interested in this, right? And we did it.
Oh, that?
Oh, we did it.
Thank you. I was also interested in it.
Oh, I love it.
Thank you.
Thank you. I thought I was about to go there.
Ah, that's it.
Oh, that's okay.
Did it, yes?
Yeah, I thought I was about to go there.
So, this is what we use. You had to be sent off today?
Uh, I don't know. We're in this, but I think I just made this a nice one.
I don't know. I don't know.
I got used to saying something.
This is a nice lot, guys.
Yeah, thank you.
Um, and let us know.
How do we think you about getting into the intro to...
Do you like intro? Or do you want something more deeper?
If he's had some other stuff?
So, I'm not sure what. Have you had some heavy air?
What time do you want to back in?
In the intro?
Uh, by no one.
Or about back, back, back.
We did it in the description.
Good up for the air?
No, no.
Yes.
Oh, thank you.
Yes.
Yes, yes.
It's kind of fun here.
They didn't have anything else.
Yeah, so, uh, I can send.
So, I can send.
Okay, I guess so.
Thank you. Sorry.