HNHacker News
TopNewBestAskShowJobs

benesch

1,289 karma · joined December 12, 2012

cto @ turbopuffer

github.com/benesch

nikhil.benesch@gmail.com

[ my public key: https://keybase.io/benesch; my proof: https://keybase.io/benesch/sigs/XTirUcJbA7dNwrQRe_-fze5YiXsPDPIs2sMq862fyDE ]

submissionscomments
benesch··on The next generation of Materialize
The biggest problem we've encountered with existing tools in the Kafka ecosystem (and the homegrown solutions that we've seen) is that nearly all of them sacrifice consistency. Debezium and most other Kafka Connect plugins will produce duplicate records upon restart, for example, that are very difficult to correctly deduplicate downstream. Things look right when you first turn on the plugin, but a week later when your Kafka Connect cluster restarts, a bit of incorrectness seeps in.

Materialize, by contrast, has been explicitly designed to preserve the consistency present in your upstream system. Our PostgreSQL source, for example, ensures that transactions committed to PostgreSQL appear atomically in Materialize, even when those transactions span multiple tables. See our "consistency guarantees" docs for some more information [0]. We have some additional features coming down the pipe, too, like allowing you to guarantee that your queries against Materialize reflect the latest data in your upstream sources [1].

[0]: https://materialize.com/docs/unstable/overview/isolation-lev...

[1]: https://github.com/MaterializeInc/materialize/issues/11531

benesch··on The next generation of Materialize
> well, minimal SQL support - there's no RETURNING, ON CONFLICT or json

We support both `INSERT ... RETURNING` [0] and the `jsonb` data type [1]. The only feature in your list that we're actually missing is UPSERT (i.e., `INSERT ... ON CONFLICT`). We have a tracking issue [2] if you're interested in following along.

> But unfortunately they only seem to be interested in enterprise customers: Not only is it not open source, but there's no open sign up and pricing for compute nodes isn't publicly available!

Rest assured: we're working towards open sign up! We're at the very beginning of our early access period.

[0]: https://materialize.com/docs/sql/insert/#details

[1]: https://materialize.com/docs/sql/types/jsonb/

[2]: https://github.com/MaterializeInc/materialize/issues/6668

benesch··on Introducing ReadySet
Back when we announced Materialize we got the same question in reverse! You can read my response from a few years back here: https://news.ycombinator.com/item?id=22362301

Unfortunately I'm not privy to whatever improvements ReadySet has made in the past two years, so I can't comment on differences between ReadySet and Materialize. Perhaps Jon can, though!

benesch··on The world of PostgreSQL wire compatibility
- Materialize is not really focused on scaling because that is not really what it is for

Horizontal scalability is actually something that Materialize's underlying incremental computation engine (timely/differential dataflow) excels at. We're actively working to expose that tech in Materialize. (As in: many engineers, including me, are working on this full time; happy to answer questions!)

It is too bad that you got the wrong impression from that blog post! That blog post is about two years out of date. We're working on an updated roadmap blog post. The tl;dr is that we've since moved from step 3 on the roadmap ("Build a single-node database management system") to step 4 ("Build cloud-native elasticity and replication: Materialize Cloud").

benesch··on 5% of 666 Python repos had comma typo bugs (inc V8, TensorFlow and PyTorch)
> This change would break a lot of legacy code for no good reason

Preventing a bug that occurs in 5% of observed codebases (and anecdotally, happens to me during development all the time) seems like about as good as reasons get.

Swapping a perfectly fine print statement for a function, on the other hand… that’s the breaking change in Py3k that’s never seemed worth it to me.

benesch··on It looks like a product but is secretly a subscription
Funny, I bought a Brother HL-1240 for $15 off of Craigslist five years ago and it’s still going strong. Couldn’t be happier. So much so that I bought a USB-Ethernet adapter (https://www.iogear.com/product/GPSU21/) to avoid having to upgrade to a newer model with Ethernet/WiFi included.

In the last five years I have printed a few hundred pages, experienced <5 paper jams, and spent $0 on ink. (The guy I bought the printer from threw in a toner cartridge for free. But I’m still on the original cartridge.)

benesch··on Malloc broke Serenity's JPGLoader, or: how to win the lottery
No, the OP is correct: the Go developers intentionally introduced randomness into map iteration order to shake out bugs.

For evidence, see:

https://github.com/golang/go/issues/6719

https://stackoverflow.com/a/55925880

https://codereview.appspot.com/5285042

benesch··on Ask HN: Is there a way to efficiently subscribe to an SQL query for changes?
Yes, exactly. Roughly speaking you'd see something like this:

    (id, text1, text2, timestamp, diff)
    ----
    (42, before, data, Friday April 23 12:27AM, -1)
    (42, after, data, Friday April 23 12:27AM, +1)
benesch··on Introducing dbt + Materialize
So, roughly speaking, I think you're describing a view that could be defined in Materialize like so:

    CREATE MATERIALIZED VIEW customer_avg_latency
    SELECT customer_id, metric_id, avg(metric_val)
    FROM metrics
    JOIN resources ON metrics.resource_id = resources.id
    JOIN customers ON resources.customer_id = customer.id
    GROUP BY customer_id, metric_id
There are various ways to slice and dice that query as a user that'll allow you to choose whether to do the aggregate early or late. The details, I think, depend on the specifics of your data model.

> When a resource migrates between customers, the whole history of the customer changes.

I'm a bit confused about this part. In the query I posted above, Differential's incremental join algorithm would spare you from recomputing the entire join. (As written the query would have to re-aggregate the metrics associated with that resource that moved, but that could be solved by manually pushing the avg down.)

If you'd like to drop by our Community Slack (https://materialize.com/s/chat) or GitHub issues (https://github.com/MaterializeInc/materialize/issues) we'd be happy to talk through things in more detail!

benesch··on Introducing dbt + Materialize
What you're asking about is the magic at the heart of Materialize. We're built atop an open-source incremental compute framework called Differential Dataflow [0] that one of our co-founders has been working on for ten years or so.

The basic insight is that for many computations, when an update arrives, the amount of incremental compute that must be performed is tiny. If you're computing `SELECT count(1) FROM relation`, a new row arriving just increments the count by one. If you're computing a `WHERE` clause, you just need to check whether the update satisfies the predicate or not. Of course, things get more complicated with operators like `JOIN`, and that's where Differential Dataflow's incremental join algorithms really shine.

It's true that there are some computations that are very expensive to maintain incrementally. For example, maintaining an ordered query like

    SELECT * FROM relation ORDER BY col
would be quite expensive, because the arrival of a new value will change the ordering of all values that sort greater than the new value.

Materialize can still be quite a useful tool here, though! You can use Materialize to incrementally-maintain the parts of your queries that are cheap to incrementally maintain, and execute the other parts of your query ad hoc. This is in fact how `ORDER BY` already works in Materialize. A materialized view never maintains ordering, but you can request a sort when you fetch the contents of that view by using an `ORDER BY` clause in your `SELECT` statement. For example:

    CREATE MATERIALIZED VIEW v AS SELECT complicated FROM t1, t2, ... -- incrementally maintained
    SELECT * FROM v ORDER BY col LIMIT 5                              -- order and limit computed ad hoc, but still fast
[0]: https://github.com/TimelyDataflow/differential-dataflow
benesch··on Materialize Raises a $32M Series B
Dremio is a batch processor, not a stream processor. The fundamental difference is that a batch processor will need to recompute a query from scratch whenever the input data changes, while a stream processor can incrementally update the existing query result based on the change to the input.

This can make a huge difference when making small changes to large datasets. Materialize can incrementally compute small changes to very complicated queries in just a few milliseconds, while with batch processors you're looking at latency in the hundreds of milliseconds, seconds, or minutes, depending on the size of the data.

Another way of looking at it is that in batch processors, latency scales with the size of the total data, while in stream processors, latency scales with the size of the updates to the data.

benesch··on Materialize Raises a $32M Series B
(Disclaimer: I'm one of the engineers at Materialize.)

> How does a company get funding for a feature that has already existed in other DBs for at least a decade? ... It's a cool concept but comes with huge caveats.

I think you answered your own question here. Incrementally-maintained views in existing database systems typically come with huge caveats. In Materialize, they largely don't.

Most other systems place severe restrictions on the kind of queries that can be incrementally maintained, limiting the queries to certain functions only, or aggregations only, or only queries without joins—or if they do support maintaining joins, often the joins must occur only on the involved tables' keys. In Materialize, by contrast, there are approximately no such restrictions. Want to incrementally-maintain a five-way join where some of the join keys are expressions, not key columns? No problem.

That's not to say there aren't some caveats. We don't yet have a good story for incrementally-maintaining queries that observe the current wall-clock time [0]. And our query optimizer is still young (optimization of streaming queries is a rather open research problem), so for some more complicated queries you may not get the resource utilization you want out of the box.

But, for many queries of impressive complexity, Materialize can incrementally-maintain results far faster than competing products—if those products can incrementally maintain those queries at all.

The technology that makes Materialize special, in our opinion, is a novel incremental-compute framework called differential dataflow. There was an extensive HN discussion on the subject a while back that you might be interested in [1].

[0]: https://github.com/MaterializeInc/materialize/issues/2439

[1]: https://news.ycombinator.com/item?id=22359769

benesch··on When X-Rays were used at the Shoe Store
I’ve never understood the argument that floors and pavement are the cause of modern foot problems due to their hardness. It just doesn’t add up.

Sure, running barefoot through a well-maintained soccer field is going to be lower impact than running on pavement. But ancient humans didn’t evolve on soccer fields.

If you ever try running through unmaintained land barefoot, it’s terrible on your feet! Uneven terrain, branches and rocks everywhere... you wind up putting a ton of stress on your feet, far more than you do walking around a house barefoot.

I think it’s far more likely that a) modern life causes children not to develop strong feet from an early age, or b) that evolutionary pressures for well-functioning feet have disappeared.

In any case, I’m glad the insoles solved your problems. I just wish the field of podiatry did a better job of root cause analysis.

benesch··on Time for a WTF MySQL Moment
> I hit in Postgres recently that is terribly documented.

I'm going to have to disagree with you there. This issue is quite well documented in the "SQL Functions Returning Sets" section [0]. The relevant bit starts thusly:

> ...Set-returning functions can be nested in a select list, although that is not allowed in FROM-clause items. In such cases, each level of nesting is treated separately, as though it were a separate LATERAL ROWS FROM( ... ) item...

And there's even a note about the crazy behavior pre-PostgreSQL 10:

> Before PostgreSQL 10, putting more than one set-returning function in the same select list did not behave very sensibly unless they always produced equal numbers of rows. Otherwise, what you got was a number of output rows equal to the least common multiple of the numbers of rows produced by the set-returning functions. Also, nested set-returning functions did not work as described above; instead, a set-returning function could have at most one set-returning argument, and each nest of set-returning functions was run independently. Also, conditional execution (set-returning functions inside CASE etc) was previously allowed, complicating things even more. Use of the LATERAL syntax is recommended when writing queries that need to work in older PostgreSQL versions, because that will give consistent results across different versions.

I agree that allowing SRFs in the SELECT clause is a wart that should never have been permitted, but I think the PostgreSQL docs do a pretty great job describing both the old behavior and the new behavior that has to balance backwards compatibility with sensibility.

(And, indeed, the 9.6 docs have this to say on the behavior of SRFs in the SELECT list: "The key problem with using set-returning functions in the select list, rather than the FROM clause, is that putting more than one set-returning function in the same select list does not behave very sensibly.")

I do think one notable defect with the PostgreSQL docs is that they were designed in a time before modern search engines. They are better understood as a written manual in electronic form. Almost always the information you need is there, but possibly not in the chapter that Google will surface. But there are all sorts of tricks you can use if you update your mental model of how to read the PostgreSQL docs. For example, there's an old-style index! [1]

[0]: https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC... [1]: https://www.postgresql.org/docs/current/bookindex.html

benesch··on Zig's New Relationship with LLVM
Oh, wow, I didn't realize that incremental compiles for Chromium were what had inspired you to write Ninja! I'm a huge fan of Ninja.

Composing a project of multiple shared libraries is a trick I've wanted to employ many times. Unfortunately neither Rust nor Go—the languages in which the two largest software projects I've worked on professionally are written—supports that model. (At least, not without throwing away the language-default build system, which has a lot of other costs.)

benesch··on Zig's New Relationship with LLVM
I wouldn't expect that to matter much during the debug cycle. The whole binary is going to be sitting in the buffer cache.
benesch··on Zig's New Relationship with LLVM
Were you working on a C/C++ project, out of curiosity? Of the two projects I've meaningfully benchmarked gold on, one was written in Rust and one was written in Go. I can believe that the Rust/Go toolchains make life difficult enough for gold that there is less of a speedup realized.

And yeah, the `--incremental` support in gold is definitely very exciting. Unfortunately it is not complete enough that I've been able to use it on any of the projects where it would matter. Part of what gets me so excited about having first-class support in Zig is that it will work end-to-end, and adjust folks' expectations of how fast linking should be. Then those folks will go demand the same linking speeds of their C/C++/Rust/Go projects.

Building incremental support into LLVM/gcc/gold is definitely the long-term answer, but building an end-to-end incremental toolchain in Zig from scratch to prove the concept makes a ton of sense.

benesch··on Zig's New Relationship with LLVM
That's not a stupid question. That's extremely insightful!

That is exactly my model of the world, in fact. When linking incrementally, you don't want a tightly packed object file where the functions are stacked right on top of one another. You want something more akin to a key–value database, where you can swap out key–value pairs without corrupting the entire file. Whether that's an object file that "symlinks" a bunch of other object files, or is one large file with easily swappable blocks (like a SQLite database) probably involves evaluating tradeoffs that I don't have a lot of context on.

(For example, one benefit of keeping it all in one file is that you can send the binary to someone else or move it to another path without corrupting the file.)

benesch··on Zig's New Relationship with LLVM
Yes, yes, yes, yes, yes.

I'm going to make the kind of prediction that will set me up to be the laughingstock of HN in a few years.

I think in-place binary patching will be the single most consequential development in build toolchains in the last twenty years; the most consequential development since a graduate student at the University of Illinois named Chris Lattner decided to embark on LLVM. If Zig is successful in this endeavor, by the end of this decade I expect that in-place binary patching will be a feature that comes standard with most build toolchains.

When you reason about build systems from first principles, it becomes abundantly clear that our current approach to linking is nuts. In both my current job and my last job, linking was by far the bottleneck in the development cycle, and a nearly unfixable one. Nothing puts a damper on getting into the zone like a sixty second link. Change a string literal somewhere? Wait sixty seconds. Fix a one-line bug in the test you just wrote? Wait sixty seconds. Change the guts of a function, but not its public interface? Wait sixty seconds.

Compilation is massively parallel, so if your compiler is slow, you have options. You can buy a bigger computer with more cores. You can outsource your compilation to the cloud. You can arrange your code cleverly to minimize dependencies, so changing one package doesn't result in invalidating too many downstream packages.

But if linking is slow, you're pretty stuck. Linking always happens, linking always happens last, and linking always depends on the output of all previous steps. Linking is rarely parallel and rarely incremental. You can switch from macOS to Linux, because Apple skimps on their linker. You can switch from the standard GNU linker (bfd) to the new GNU linker (gold) [0], which has both built-in parallelism and an incremental mode. But gold is only 20-30% faster.

I'm not excited by a 30% speedup. Saving 30% on a 60s link still means you have a brutal 42s link; still plenty long to kick you out of the flow. A 30% speedup is the kind of speedup that's erased in a year when your project is 30% larger. What I'm excited about is a speedup the size of several orders of magnitude! That's the magic that Zig is striving for here. Change a string literal? Bam! Your binary has already been relinked, before your finger even rolled off the return key.

This is the kind of thing that has the potential to change our relationship with compiled languages forever. The differences between a dynamic language like Python and a compiled language like Zig become mightly slim when recompiling a large Zig program becomes as fast as restarting a Python program.

And the only way to get there is to redesign the compiler toolchain from start to finish. You need everything from the design of the programming language down to a custom linker to cooperate. Binary formats like ELF are not designed to be hot-swappable like this; they're designed to be write-once. The same goes for debugging information like DWARF. They've developed some very clever tricks to make this work. I wouldn't be surprised if one day there's a custom, non-ELF binary format specifically for the use of incremental toolchains.

Huge props to Andy Kelley and the Zig team for having the vision and the guts to embark on this quest. (That reminds me: I am long overdue on setting up a sponsorship for the Zig Software Foundation [1] to fund this sort of work!)

[0]: https://en.wikipedia.org/wiki/Gold_(linker)

[1]: https://ziglang.org/zsf/

benesch··on SQLite 3.33
No, PostgreSQL definitely lets you do this.

> An expression used inside a grouping_element can be an input column name, or the name or ordinal number of an output column (SELECT list item), or an arbitrary expression formed from input-column values. [0]

> Each expression can be the name or ordinal number of an output column (SELECT list item), or it can be an arbitrary expression formed from input-column values. [1]

Perhaps you are thinking of trying to use an output column name in a WHERE clause?

[0]: https://www.postgresql.org/docs/current/sql-select.html#SQL-...

[1]: https://www.postgresql.org/docs/current/sql-select.html#SQL-...

benesch··on Linux kernel in-tree Rust support
But the translation unit in Rust is the crate. Running `rustc` directly won't change that fact. Unlike in C, where you can compile TUs in any order since you've got interfaces in headers, in Rust you can't compile a crate until you've compiled all its dependencies--and if it's not Cargo that's going to sort that out for you, Kbuild will have to.
benesch··on Linux kernel in-tree Rust support
Part of what makes rustc's slow compile times so frustrating is that the slow compile times are mostly unrelated to all of the fancy compile-time analysis you listed. You can see this in action if you run `cargo check`. That will run all the special static analysis (typeck, borrowck, dropck, etc.) and complete in a very reasonable amount of time.

To a first approximation, rustc is slow because it generates terrible LLVM IR, and relies on a number of slow LLVM optimization passes to smooth that IR into something reasonable. As far as I can tell this is entirely a result of not having the necessary engineering resources to throw at the problem; Mozilla has a limited budget for Rust, and needs to spread that budget across not just the compiler toolchain, but the language and ecosystem.

Brian Anderson's been working on a blog post series that covers Rust's slow compile times in more detail, if you're curious: https://pingcap.com/blog/tag/Rust

benesch··on SELECT wat FROM sql
In PostgreSQL, the grammar does not permit unquoted identifiers to start with a digit. So this is a syntax error:

    SELECT 'val' AS 2
Instead you must escape the identifier with double quotes:

    SELECT 'val' AS "2"
So there is no ambiguity on that front in the ORDER BY clause. Either you have `ORDER BY 2`, which means order by the second column, or you have `ORDER BY "2"`, which means order by the column named "2".
benesch··on SELECT wat FROM sql
You can do that in PostgreSQL, but not according to the spec, apparently, and the PostgreSQL developers regret the feature. [0] (As someone who's spent a lot of time trying to simulate PostgreSQL's name resolution rules... it's reassuring to know that the rules are painful inside of PostgreSQL too.)

[0]: https://www.postgresql.org/message-id/7608.1259177709%40sss....

benesch··on SELECT wat FROM sql
That is not correct. The parent has it right. The output is in fact sorted by the second field; it is not random.

> Each expression can be the name or ordinal number of an output column (SELECT list item), or it can be an arbitrary expression formed from input-column values.

> The ordinal number refers to the ordinal (left-to-right) position of the output column. This feature makes it possible to define an ordering on the basis of a column that does not have a unique name. This is never absolutely necessary because it is always possible to assign a name to an output column using the AS clause.

(emphasis mine)

https://www.postgresql.org/docs/current/sql-select.html#SQL-...

benesch··on SELECT wat FROM sql
You're not looking at the examples closely enough, if that's your takeaway. I assure you they are much weirder than forgetting to specify an ORDER BY clause and expecting sorted output.
benesch··on SELECT wat FROM sql
I highly recommend puzzling through some of these on your own first (it's a great way to build intuition for SQL's craziness), but here's a few explanations if you're stuck.

    jamie=# select a+1 from nums group by a+1;
     ?column?
    ----------
            2
            4
            1
            3
    (4 rows)
    
    jamie=# select a+1 from nums group by 1+a;
    ERROR:  column "nums.a" must appear in the GROUP BY clause or be used in an aggregate function
    LINE 1: select a+1 from nums group by 1+a;
In general, a query like `SELECT a FROM t GROUP BY a` is valid, while a query `SELECT b FROM t GROUP BY a` is not valid. Because you didn't group by column b, there are multiple values of b within each group, and it's not clear how to combine them. What you usually meant is something like

    SELECT a, sum(b) FROM t GROUP BY a
where you can see the sum of all b's for each distinct value of a.

The error message explains it really nicely: if there is a GROUP BY clause, every column in a SELECT list must either be used in the GROUP BY clause or appear in an aggregate function. This is kind of annoying, though; what if you want to group by a value derived from a column, like `a + 1`? (More realistically, maybe you want to trim whitespace or something.) I think the SQL standard would have you write

    SELECT a1 FROM (SELECT a + 1 AS a1 FROM t) _ GROUP BY a1
but that's annoying. PostgreSQL (and maybe other SQL implementations; not sure!) has some smarts about detecting when an expression is used verbatim in a SQL query, so you can just write

    SELECT a+1 FROM t GROUP BY a+1
and it will notice that the "a+1" in the SELECT list is literally the same as the "a+1" in the GROUP BY clause. But it's not that smart, because it doesn'that realize that "a+1" and "1+a" are semantically equivalent, and so this is an error:

    SELECT a+1 FROM t GROUP BY 1+a
----

    jamie=# select b from nums group by a;
     b
    ---
    (0 rows)
    
    jamie=# select a from nums group by b;
    ERROR:  column "nums.a" must appear in the GROUP BY clause or be used in an aggregate function
    LINE 1: select a from nums group by b;
An extension of the previous example, where PostgreSQL can infer that when grouping by a all b's are unique. It can't infer the converse. The reason is that a is the primary key and so guaranteed to be unique, and therefore `GROUP BY a` is meaningless and can be elided.

(This one really confused me for a while. I only learned this recently.)

----

    jamie=# select a, b from nums order by 2;
    a |  b
    ---+-----
     3 | bar
     0 | foo
     1 | foo
     2 | foo
    (4 rows)
    
    jamie=# select a, b from nums order by 1+1;
     a |  b
    ---+-----
     0 | foo
     1 | foo
     2 | foo
     3 | bar
    (4 rows)
`ORDER BY <n>` means "order by the Nth column", but `ORDER BY <expr>` means "order by the value of <expr>`. So you get the goofy behavior that `ORDER BY 2` in the above query means order by column b, but `ORDER BY 1 + 1` means order by the literal value "2", i.e., don't order by anything useful at all. (It just so happens that you get primary key order in the example above, but that's not guaranteed by any means.)

----

Most of the others have a similar feel. Let me know if you're particularly puzzled by any others.

benesch··on Timely Dataflow in Fifteen Minutes [video]
Yes, we consider ourselves an “open core” company. Timely and differential, the core compute engine, are fully open source projects, but the Materialize layer atop is licensed under the “Business Source License” (BSL).

We think the BSL strikes a good balance between giving back to the community—four years after every release, the code is automatically relicensed under Apache 2—and ensuring we can build a viable business. And you’re free to use Materialize for any purpose in a non-distributed (i.e., single node) deployment without paying for an enterprise license.

benesch··on Materialize: A Streaming Data Warehouse
Good catch, thanks! I've filed an issue about this: https://github.com/MaterializeInc/materialize/issues/2056

Though it's not yet documented, we do support all of the SQL set operations [0]: UNION [ALL], INTERSECT [ALL], and EXCEPT [ALL].

[0]: https://www.postgresql.org/docs/current/queries-union.html

benesch··on Materialize: A Streaming Data Warehouse
> For example, if I rely on "updated-at" and infer that whatever record has the latest updated-at is the "current" record, then I may create the illusion that there are no gaps in my facts. That may not be so.

I believe that notion is captured by timely's capabilities [0]. Your capability has a current time, and you can only produce records at or greater than the current time. So you could produce a record at, say, t + 3, then t + 5, and then produce a record at t + 1. But not until you downgrade your capability to t + 6 will the record at t + 5 be considered final; downgrading your capability is how you indicate that you have the correct and final set of facts for all times less than t.

If your events can arrive out of order forever, then you have a problem, as you'll never be able to downgrade your capability because you'll never be willing to mark a time as "final." That's where bitemporalism (as mentioned in that issue I linked previously) comes into play. You can mark a result as final as of some processing time, and then issue corrections as of some processing time in the future if some out-of-order data arrives. Materialize will (likely) gain support for bitemporalism eventually, and the underlying dataflow engine supports arbitrary-dimension timestamps already.

Would be happy to chat about this more, if you're curious, but I feel like this discussion is getting a bit unwieldy for an HN thread! (At the very least I might need to put you in touch with Frank.) Feel free to reach out on GitHub [1] or our Gitter [2], or shoot me an email at benesch@materialize.io.

[0]: https://docs.rs/timely/0.11.1/timely/dataflow/operators/stru...

[1]: https://github.com/MaterializeInc/materialize/issues

[2]: https://gitter.im/MaterializeInc/community

← PreviousPage 2 of 7Next →