Skip to content

sql: implement a memory policy that prevents server crashes for large queries#9259

Merged
knz merged 2 commits intocockroachdb:masterfrom
knz:mem-pool
Oct 31, 2016
Merged

sql: implement a memory policy that prevents server crashes for large queries#9259
knz merged 2 commits intocockroachdb:masterfrom
knz:mem-pool

Conversation

@knz
Copy link
Copy Markdown
Contributor

@knz knz commented Sep 10, 2016

Prior to this patch, any query that required rows to be loaded in
server memory prior to being sent back to the client could cause the
server to crash if there is insufficient RAM to store all rows.

This patch builds upon the MemoryUsageMonitor abstraction by
providing a new MemoryPool object which serves as a gate to
allocations.

A single MemoryPool object is shared by all client connections over
pgwire. Its capacity for a given node is 25% of total node RAM size,
unless overridden via --sql-memory on that node's start command
line.

Each client connection starts with a budget of 1KiB (configurable via
the env. var. COCKROACH_BASE_SQL_MEMORY_BUDGET). This budget is
pre-reserved from the common pool by the pgwire server (using a
pgwire-wide MemoryUsageMonitor), and reused when a connection is
closed without interacting with the pool. This ensures that the pool
is not reached at all for what we expect is common usage: many clients
using only OLTP queries returning very few rows (or none) and without
need for dynamic server-side row storage.

Once a client connection exceeds its pre-reserved budget, its
session-bound MemoryUsageMonitor starts interacting with the pool by
requesting fixed-size chunks. These chunks are 10KiB by default, and
this size is configurable via the
env. var. COCKROACH_MEMORY_ALLOCATION_CHUNK_SIZE. If a session is
long-lived, a monitor will relinquish unused memory to the common pool
when there are more than 10 unused reserved chunks (this factor is
configurable via COCKROACH_MEMORY_FREE_FACTOR).

A separate MemoryPool object supports SQL allocations by the
adminServer for UI API calls and admin commands; this is currently
unbounded. Also, allocations by sql.InternalExecutor are not limited
at all (this uses SQL to obtain leases on descriptors).

Fixes #7572.


This change is Reviewable

@andreimatei
Copy link
Copy Markdown
Contributor

haven't looked at any of the clients changed here yet. Let's do one more iteration on the main classes.


Review status: 0 of 22 files reviewed at latest revision, 24 unresolved discussions, some commit checks failed.


cli/flags.go, line 329 at r1 (raw file):

      sqlSize = newBytesValue(&serverCtx.SQLMemoryPoolSize)
      varFlag(f, sqlSize, cliflags.SQLMem)

how are you thinking of flags vs env vars?


cli/start.go, line 117 at r1 (raw file):

      ctx.CacheSize = size / 4

      // Default the SQL memory pool size to 1/4 of total memory.  Again

two space before "again"


sql/mon/mem_pool.go, line 35 at r1 (raw file):

  mu struct {
      syncutil.Mutex
      cur int64

add unit here (and I'd add it throughout all params in this file, but I feel most strongly about this one).


sql/mon/mem_pool.go, line 51 at r1 (raw file):

  defer p.mu.Unlock()

  if p.mu.cur > p.max-extraSize {

nit: to me, if p.mu.cur + extrSize > p.max reads better


sql/mon/mem_pool.go, line 52 at r1 (raw file):

  if p.mu.cur > p.max-extraSize {
      err := errors.Errorf("memory budget exceeded: %d requested, %s already allocated",

%d bytes


sql/mon/mem_pool.go, line 52 at r1 (raw file):

  if p.mu.cur > p.max-extraSize {
      err := errors.Errorf("memory budget exceeded: %d requested, %s already allocated",

IMHO, I think for the purposes of this error msg, it's less interesting the amount that's been allocated in the pool, and more interesting what's been already allocated in the acc.


sql/mon/mem_pool.go, line 56 at r1 (raw file):

          humanizeutil.IBytes(acc.curAllocated))
      if log.V(2) {
          log.Errorf(ctx, "%s, %s global, %s global max",

is there a VErrorf() ?


sql/mon/mem_usage.go, line 30 at r1 (raw file):

)

// MemoryUsageMonitor defines an object that can track and limit

the "limit" part is wrong, correct (I think it was theoretically doing that before this PR, but not any more?)? And in fact the "tracking" is not really the point of this struct either, is it? It's all about buffering. Maybe we should rename it to... PoolProxy? MemoryBuffer?
Should it share an interface with the Pool?


sql/mon/mem_usage.go, line 38 at r1 (raw file):

// and after a processing context.
// The various counters express sizes in bytes.
type MemoryUsageMonitor struct {

As discussed, let's put a much bigger comment here explaining the interactions between Pool - Monitor - Acccount (and probably mention WrappedAccount too)


sql/mon/mem_usage.go, line 40 at r1 (raw file):

type MemoryUsageMonitor struct {
  // curAllocated tracks the current amount of memory allocated for
  // in-memory row storage.

delete the reference to "in-memory row storage".


sql/mon/mem_usage.go, line 44 at r1 (raw file):

  // totalAllocated tracks the cumulated amount of memory allocated.
  totalAllocated int64

is this member actually useful for anything? I'd vote to remove it, particularly since the comment didn't make it clear to me about what it means and I had to look it up.


sql/mon/mem_usage.go, line 46 at r1 (raw file):

  totalAllocated int64

  // maxAllocated tracks the high water mark of allocations.

maybe suggest in this comment how this is going to be used. Otherwise it seems superfluous to me.


sql/mon/mem_usage.go, line 50 at r1 (raw file):

  // curBudget sets the allowable upper limit for allocations,
  // representing the budget pre-allocated at the pool.

is this comment correct? What does "pre-allocated" mean?


sql/mon/mem_usage.go, line 53 at r1 (raw file):

  curBudget MemoryAccount

  // preBudget indicates how much memory was already reserved for this

maybe expand this comment a bit. Why has any memory been reserved before this was instantiated? Why isn't that memory just set as the initial curBudget?

later: having discussed some, I understand what the deal is. I suggest renaming the field and changing the comment - instead of "already reserved...", say that this is the amount of memory that's never released (maybe call it reserve).


sql/mon/mem_usage.go, line 59 at r1 (raw file):

  // pool specifies where to send requests to increase or decrease
  // curBudget.
  pool *MemoryPool

make this the first member (to highlight the parent-child structure, and also to celebrate the fact that you've finally embedded a pointer into a struct :) )


sql/mon/mem_usage.go, line 70 at r1 (raw file):

var noteworthyMemoryUsageBytes = envutil.EnvOrDefaultInt64("COCKROACH_NOTEWORTHY_MEMORY_USAGE", 10000)

// allocHysteresisFactor determines the maximum difference between the

if we call this "hysterisis", I say you rename the "noteworthy" above to "hysterical"


sql/mon/mem_usage.go, line 73 at r1 (raw file):

// amount of memory used by a monitor and the amount of memory
// reserved at the upstream pool before the monitor relinquishes the
// memory back to the pool.  This is useful so that a monitor

double space


sql/mon/mem_usage.go, line 76 at r1 (raw file):

// currently at the boundary of a block does not cause contention when
// accounts cause its allocation counter to grow and shrink slightly
// beyond and beneath an allocation block boundary.  The difference is

double space


sql/mon/mem_usage.go, line 78 at r1 (raw file):

// beyond and beneath an allocation block boundary.  The difference is
// expressed as a number of blocks of size `poolAllocationSize`.
var allocHysteresisFactor = envutil.EnvOrDefaultInt("COCKROACH_MEMORY_FREE_FACTOR", 10)

if it's expressed in "blocks", maybe "factor" is not the right name. "Buffer"?


sql/mon/mem_usage.go, line 95 at r1 (raw file):

  minExtra = mm.roundSize(minExtra)
  if log.V(2) {
      log.Infof(ctx, "requesting %d bytes from the pool", minExtra)

VInfof() here and throughout


sql/mon/mem_usage.go, line 102 at r1 (raw file):

// releaseBudget relinquishes all the monitor's memory back to the
// pool.
func (mm *MemoryUsageMonitor) releaseBudget(ctx context.Context) {

mmm what about the preBudget? That doesn't get released?
Does this need to be a different function from StopMonitor()?


sql/mon/mem_usage.go, line 164 at r1 (raw file):

// releaseMemory releases memory previously successfully registered
// via reserveMemory().
func (mm *MemoryUsageMonitor) releaseMemory(ctx context.Context, x int64) {

rename x to smth else


sql/mon/mem_usage_mutex.go, line 24 at r1 (raw file):

)

// MemoryUsageMonitorWithMutex encapsulates a MemoryUsageMonitor using

just say it's a thread-safe Monitor. And in the comments of the regular Monitor, say it's not thread-safe and link to this one.


sql/mon/mem_usage_mutex.go, line 26 at r1 (raw file):

// MemoryUsageMonitorWithMutex encapsulates a MemoryUsageMonitor using
// a Mutex.
type MemoryUsageMonitorWithMutex struct {

Do we really need this class? I'd just put a mutex in the other one and call it a thread-safe day. If the mutex is not contended, it shouldn't cost much.


Comments from Reviewable

@knz
Copy link
Copy Markdown
Contributor Author

knz commented Sep 29, 2016

I'm processing these comments bit by bit. Here's a first batch, gimme more time before I address the bigger ones.


Review status: 0 of 22 files reviewed at latest revision, 24 unresolved discussions, some commit checks pending.


cli/flags.go, line 329 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

how are you thinking of flags vs env vars?

I think command-line args are good for pretty big tuning knobs, and this is one. TBH I just followed the example of the cache size.

cli/start.go, line 117 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

two space before "again"

Done.

sql/mon/mem_pool.go, line 35 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

add unit here (and I'd add it throughout all params in this file, but I feel most strongly about this one).

Done.

sql/mon/mem_pool.go, line 51 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

nit: to me, if p.mu.cur + extrSize > p.max reads better

Int overflow? That's C/Go 101 for me...

sql/mon/mem_pool.go, line 52 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

%d bytes

Done.

sql/mon/mem_pool.go, line 52 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

IMHO, I think for the purposes of this error msg, it's less interesting the amount that's been allocated in the pool, and more interesting what's been already allocated in the acc.

That's what the error reports!

sql/mon/mem_pool.go, line 56 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

is there a VErrorf() ?

Not in our log package that I know of, neither anywhere else in our repo.

sql/mon/mem_usage.go, line 70 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

if we call this "hysterisis", I say you rename the "noteworthy" above to "hysterical"

:trollface:

sql/mon/mem_usage.go, line 73 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

double space

Done.

sql/mon/mem_usage.go, line 76 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

double space

Done.

sql/mon/mem_usage.go, line 95 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

VInfof() here and throughout

We don't have that in the code ATM.

sql/mon/mem_usage.go, line 164 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

rename x to smth else

Done.

sql/mon/mem_usage_mutex.go, line 24 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

just say it's a thread-safe Monitor. And in the comments of the regular Monitor, say it's not thread-safe and link to this one.

Done.

sql/mon/mem_usage_mutex.go, line 26 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

Do we really need this class? I'd just put a mutex in the other one and call it a thread-safe day. If the mutex is not contended, it shouldn't cost much.

You and your unjustified overheads... I'll just ignore you on this one. 😅

Comments from Reviewable

@knz
Copy link
Copy Markdown
Contributor Author

knz commented Sep 30, 2016

Finished the entire first batch of comments, ready for the next one.


Review status: 0 of 23 files reviewed at latest revision, 24 unresolved discussions.


sql/mon/mem_usage.go, line 30 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

the "limit" part is wrong, correct (I think it was theoretically doing that before this PR, but not any more?)? And in fact the "tracking" is not really the point of this struct either, is it? It's all about buffering. Maybe we should rename it to... PoolProxy? MemoryBuffer?
Should it share an interface with the Pool?

It can limit it if there is no pool attached. The name "monitor" is appropriate since one of the main motivations is to track (monitor) the usage. See the newly minted explanatory comment.

sql/mon/mem_usage.go, line 38 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

As discussed, let's put a much bigger comment here explaining the interactions between Pool - Monitor - Acccount (and probably mention WrappedAccount too)

Done.

sql/mon/mem_usage.go, line 40 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

delete the reference to "in-memory row storage".

Done.

sql/mon/mem_usage.go, line 44 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

is this member actually useful for anything? I'd vote to remove it, particularly since the comment didn't make it clear to me about what it means and I had to look it up.

Explained in comments.

sql/mon/mem_usage.go, line 46 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

maybe suggest in this comment how this is going to be used. Otherwise it seems superfluous to me.

Done.

sql/mon/mem_usage.go, line 50 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

is this comment correct? What does "pre-allocated" mean?

Clarified in comment.

sql/mon/mem_usage.go, line 53 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

maybe expand this comment a bit. Why has any memory been reserved before this was instantiated? Why isn't that memory just set as the initial curBudget?

later: having discussed some, I understand what the deal is. I suggest renaming the field and changing the comment - instead of "already reserved...", say that this is the amount of memory that's never released (maybe call it reserve).

Done.

sql/mon/mem_usage.go, line 59 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

make this the first member (to highlight the parent-child structure, and also to celebrate the fact that you've finally embedded a pointer into a struct :) )

Not so fast, a monitor can also be standalone and a nil pool is valid. Clarified in comments.

sql/mon/mem_usage.go, line 78 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

if it's expressed in "blocks", maybe "factor" is not the right name. "Buffer"?

Done.

sql/mon/mem_usage.go, line 102 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

mmm what about the preBudget? That doesn't get released?
Does this need to be a different function from StopMonitor()?

See comments about the preBudget.

Comments from Reviewable

@tbg tbg closed this Oct 3, 2016
@andreimatei
Copy link
Copy Markdown
Contributor

Tobi, you can't just close things that clutter your inbox.

@tamird tamird changed the base branch from develop to master October 3, 2016 14:49
@tamird tamird reopened this Oct 3, 2016
@andreimatei
Copy link
Copy Markdown
Contributor

Review status: 0 of 23 files reviewed at latest revision, 10 unresolved discussions, some commit checks failed.


sql/mon/mem_pool.go, line 51 at r1 (raw file):

Previously, knz (kena) wrote…

Int overflow? That's C/Go 101 for me...

well cases where you need to protect against overflow are a small minority and probably require explanations. In particular here you're using int64, so that's not an issue. Anyway, just a nit, write it as you wish.

sql/mon/mem_pool.go, line 31 at r2 (raw file):

// for memory.
// The API for this class is:
// - use a MemoryAccount per client to the pool;

nit: per client of the pool


sql/mon/mem_usage.go, line 44 at r1 (raw file):

Previously, knz (kena) wrote…

Explained in comments.

but is this useful at all? Like, have you ever needed such a gauge for a process in Unix? Maybe if you'd compute it as some sort of a rate... My vote is still for removing it.

sql/mon/mem_usage.go, line 50 at r1 (raw file):

Previously, knz (kena) wrote…

Clarified in comment.

I still think "pre-allocated" doesn't mean anything here... This is the amount of memory _currently_ allocated for this monitor in the parent pool, right?

sql/mon/mem_usage.go, line 53 at r1 (raw file):

Previously, knz (kena) wrote…

Done.

you're going to hate me :) See, this `Monitor` should take ownership of this `preBudget` and should be responsible for releasing it on `Close`. Of course, you can't release it back to `mon.pool`, cause the memory might have come from some other pool. So I guess this should be a `WrappedAccount` that we take ownership of (if all accounts were `WrappedAccount`s, we wouldn't need to have this conversation :) ). I think the way this `preBudget` is currently closed by the `v3 session` is awkward. Now there's two objections that need finalizing - the session, which closes the session's monitor, and this other "base account". And they're actually finalized in the wrong order - the base account is closed first (in v3conn.serve), and the monitor second (v3Server.ServeConn).

And I'm still not big on the name preBudget. Still suggest ...reserve.


sql/mon/mem_usage.go, line 95 at r1 (raw file):

Previously, knz (kena) wrote…

We don't have that in the code ATM.

Sorry, `VTracef()`

sql/mon/mem_usage.go, line 46 at r2 (raw file):

//   more instances of MemoryAccount, one per "category" of
//   allocation, and issue requests to Grow, Resize or Close to their
//   monitor.  Grow/Resize requests can be denied (return an error),

nit: double space (here and in the next paragraph). I've considered shutting up about these, and I probably should, but it hurts my eyes more than other stuff and I'm convinced that someone else will either complain now if they read this, or be compelled to fix later. But feel free to ignore :)


sql/mon/mem_usage.go, line 172 at r2 (raw file):

// Finally, a simplified API is provided in session_mem_usage.go to
// simplify the interface offered to SQL components using memory
// accounts linked to the session-bound monitor.

call out WrappedAccount by name?


sql/mon/mem_usage_mutex.go, line 26 at r1 (raw file):

Previously, knz (kena) wrote…

You and your unjustified overheads... I'll just ignore you on this one. 😅

I guess I can't win them all. I'd suggest at least moving this guy next to `MemUsageMonitor`.

Comments from Reviewable

@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 11, 2016

Review status: 0 of 23 files reviewed at latest revision, 10 unresolved discussions.


sql/mon/mem_pool.go, line 51 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

well cases where you need to protect against overflow are a small minority and probably require explanations. In particular here you're using int64, so that's not an issue. Anyway, just a nit, write it as you wish.

It is an issue because if some client code passes an abnormally large value for extraSize we need to be able to catch it. But anyway I added a comment.

sql/mon/mem_pool.go, line 31 at r2 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

nit: per client of the pool

Done.

sql/mon/mem_usage.go, line 44 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

but is this useful at all? Like, have you ever needed such a gauge for a process in Unix? Maybe if you'd compute it as some sort of a rate... My vote is still for removing it.

Yes this would be used to compute average allocations per second if we sample asynchronously. But whatever, it can be re-added later when we need it.

sql/mon/mem_usage.go, line 50 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I still think "pre-allocated" doesn't mean anything here... This is the amount of memory currently allocated for this monitor in the parent pool, right?

Correct. I used this phrasing in the comment now.

sql/mon/mem_usage.go, line 53 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

you're going to hate me :)
See, this Monitor should take ownership of this preBudget and should be responsible for releasing it on Close. Of course, you can't release it back to mon.pool, cause the memory might have come from some other pool. So I guess this should be a WrappedAccount that we take ownership of (if all accounts were WrappedAccounts, we wouldn't need to have this conversation :) ).
I think the way this preBudget is currently closed by the v3 session is awkward. Now there's two objections that need finalizing - the session, which closes the session's monitor, and this other "base account". And they're actually finalized in the wrong order - the base account is closed first (in v3conn.serve), and the monitor second (v3Server.ServeConn).

And I'm still not big on the name preBudget. Still suggest ...reserve.

I'll sleep over it.

sql/mon/mem_usage.go, line 95 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

Sorry, VTracef()

It's VEventf. I sprinkled a bit of that here and there. However I won't use it whenever `GetSmallTrace()` or `humanize()` is used in the arguments because Go would force the arguments to be evaluated even when logging is effectively disabled.

sql/mon/mem_usage.go, line 46 at r2 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

nit: double space (here and in the next paragraph). I've considered shutting up about these, and I probably should, but it hurts my eyes more than other stuff and I'm convinced that someone else will either complain now if they read this, or be compelled to fix later. But feel free to ignore :)

Done.

sql/mon/mem_usage.go, line 172 at r2 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

call out WrappedAccount by name?

Done.

sql/mon/mem_usage_mutex.go, line 26 at r1 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I guess I can't win them all. I'd suggest at least moving this guy next to MemUsageMonitor.

What do you mean? Is it not enough to have it in the same directory?

Comments from Reviewable

@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 16, 2016

I have performed the suggested refactorings, and some more. Included in this new revision:

  • MemoryUsageMonitor renamed to MemoryMonitor
  • MemoryPool goes away, MemoryMonitors can be stacked
  • now tracking metrics separately for regular SQL clients, admin queries and internal executor
  • viz in admin UI.

PTAL!

@knz knz force-pushed the mem-pool branch 2 times, most recently from 65f46cb to 90b3db2 Compare October 17, 2016 12:03
@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 19, 2016

Note that I am rebasing this on top of #10094, so I will squash the additional 2 commits after #10094 is merged.


Review status: 0 of 39 files reviewed at latest revision, 20 unresolved discussions, all commit checks successful.


pkg/cli/flags.go, line 329 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

why did you need to introduce this named variable?

What do you call a "named variable"?

pkg/server/admin.go, line 48 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

nit: I think we like putting these before cockroach imports

Done.

pkg/sql/mem_metrics.go, line 26 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

please explain "SQL endpoint"

Done.

pkg/sql/mem_metrics.go, line 27 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I'm not familiar with how we export other metrics from our code, but may I suggest a pattern that makes more sense to me:

Instead of accumulating all the metrics that we want to export in this structure, and then passing parts of this structure to the constructors of the various monitors, how about we have each monitor (optionally) instantiate its own metrics? The monitor already has a name that it can prepend to the metric name.
That would clean up the monitor's constructor. And... scale.

Nah: 1) the metric objects are actually shared by all constructors at the same level 2) they must be registered via a call to registry.AddMetricStruct() which exists only in the server initialization code. Plumbing the registry to every monitor would be a nightmare.

pkg/sql/mem_metrics.go, line 80 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

are these 3 factories different? can't we just have one?

Done.

pkg/sql/mon/mem_usage.go, line 59 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I sense a new table in the session_info virtual database coming!

I'll take your word for it :)

pkg/sql/mon/mem_usage.go, line 69 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

there's still a few references to "pool", here and elsewhere. Maybe say that a "shared monitor" (or a monitor that feeds other monitors) is aka "pool".

Done.

pkg/sql/mon/mem_usage.go, line 204 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

You said you'd sleep on the discussion we were having around this member, and then on the PR you said you've done some refactoring, but I don't see it :)
In fact, I like it even less than before haha. When it was an Account, that at least was conducive to the conversation about who owns it. Now it's just an int that's hard (for me, at least) to track. We've introduced one extra opportunity for usage error: the constants used in connMonitor.GrowAccount(ctx, &c.baseMemAcc, baseSQLMemoryBudget) and c.session.StartMonitor(connMonitor, baseSQLMemoryBudget) need to correspond to one another, otherwise you've just leaked memory. Ironically, the whole point of this work is to provide good interfaces for accounting.

So let's resume that conversation: what makes sense to me is to have this Monitor take ownership of this "reserved" memory, since the memory is tied to the lifetime of the monitor. The memory should be released on StopMonitor(). Why have the caller retain ownership of this bytes/account and have it worry about when to release them?
I think the following argument should drive the point home: the usage of this reserved bytes is already technically incorrect. The reserved account is Close()d before the monitor is Stop()ed ! The account is closed first, in v3Conn.Serve(), and the monitor is closed second, in pgwire.Server.ServeConn()

That couldn't happen if the monitor took ownership.

Ok I took some time to study this comment and I see several issues that need untangling.
  1. regarding the order in which things happen in pgwire. I think it is a logic error to set up the Session object before the connection is up and authenticated (we don't want clients touching the executor before we know they're legit), so I changed that.

  2. your comment highlighted a logic error that I have introduced in my refactoring, which I have now fixed. Thanks for bringing that up. Let me clarify: there is one top level shared monitor sqlMemoryPool. Then pgwire has one connMonitor attached to sqlMemoryPool; this takes care of "upfront reservations". Then separately there is one monitor per session, also attached to sqlMemoryPool (and thus has connMonitor as sibling, not parent). So whenever we think about ownership transfer here we have to be clear that this is transfer between siblings, not a (relatively simpler) transfer from parent to child.

  3. I probably need to find a better name than "reserved". As long as I keep using the names "prebudget" or "reserved" you will keep thinking about accounts. But I still believe this is not the right way to think about this. To me, "reserved" is an amount dedicated to that monitor and not necessarily owned by another monitor. Remember, when I killed MemoryPool I brought the role of "root provider" into the responsibility of the monitor. For root monitors like sqlMemoryPool that reserved amount comes out of nothing. If there was an account for this, there would be no monitor to release it to, and worse, there would be no monitor to allocate it from.

If you find a good way to solve that last problem, in particular if you can tell me which invocation of Start() you would propose to use for sqlMemoryPool in pgwire/server.go, I'll be willing to look at this further.


pkg/sql/mon/mem_usage.go, line 238 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I think it's weird that StartMonitor is a method. I think generally this would be a NewMonitor factory is our code, since it initializes everything. You can't really use a monitor instance in any way before doing all this initialization.

Yes but no. The monitor is embedded in session so you would need to do `session.txnMon = mon.MakeMonitor(...)` at the start of every transaction, but this is invalid because the monitor contains a mutex. I did rename the method as suggested though.

pkg/sql/mon/mem_usage.go, line 468 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

what is this protecting against? Double stop? If so, let's just error.

It's just to deal with the special case of a monitor being stopped without having been used at all. But you're right this check was misplaced, so I removed it.

sql/mon/mem_usage.go, line 333 at r2 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

nit: Start/Stop instead of StartMonitor/StopMonitor.

Done.

Comments from Reviewable

@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 19, 2016

Here are two screenshots of the UI additions:

  • a new "SQL Memory" graph in the "System Resources" section per node:
    schermafdruk_2016-10-20_01-21-49
  • detailed graphs with histograms for the clients, admin and internal SQL endpoints in the "Advanced" section:
    schermafdruk_2016-10-20_01-19-47

@knz knz force-pushed the mem-pool branch 4 times, most recently from b3d5ef1 to 715eabc Compare October 26, 2016 00:46
@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 26, 2016

PTAL.
As discussed yesterday I have added a new BoundAccount to solve the ownership transfer of the reserved budget.
Also I have added one of the two acceptance tests we discussed. Please check this is going in the right direction? I still have to think some more about the other one.

@andreimatei
Copy link
Copy Markdown
Contributor

nit: I don't think the "admin" and "internal" mem usage should be plotted on the "System Resources" section; just in the Advanced?

Looking good man, thanks for working with me. I just suggest a few improvements (IMHO) that I think would be worth-while.


Review status: 0 of 41 files reviewed at latest revision, 22 unresolved discussions, some commit checks failed.


pkg/cli/flags.go, line 329 at r3 (raw file):

Previously, knz (kena) wrote…

What do you call a "named variable"?

i don't know what this was about. prolly reading comprehension failure on my part.

pkg/cli/interactive_tests/test_sql_mem_monitor.tcl, line 1 at r7 (raw file):

#! /usr/bin/env expect -f

it's really nice that this works!

I was hoping we'd write this as an acceptance test. tcl is... exotic and creeping up in scope :)
In any case, the test deserves a comment about what it tests and what it'll do.


pkg/cli/interactive_tests/test_sql_mem_monitor.tcl, line 52 at r7 (raw file):

send "set database=information_schema;\r"
eexpect root@
send "select * from columns as a, columns as b, columns as c, columns as d limit 10;\r"

is this an outer join?


pkg/cli/interactive_tests/test_sql_mem_monitor.tcl, line 66 at r7 (raw file):

# Re-launch a server with very low limit for SQL memory
set spawn_id $shell_spawn_id
send "$argv start --max-sql-memory=30K\r"

ah, see, this is the crux of it, I think: I think part of the point of this test (or, rather, the point of all your work in general) has to be that the user should be able to set a limit that has something to do with the ulimit (ideally, exactly the ulimit), and our server is guaranteed to not crash. Like, it's "easy" to not crash if you refuse every query; the difficulty is in running close to your limit (so, having good utilization of the "hardware") and not crashing. Living on the edge.
I guess it's too early for that, since the rocksdb memory is not integrated with this your flag at all (although it does have its own flags), but at least aspirationally that's the dream, right? So maybe add a TODO here.


pkg/sql/mem_metrics.go, line 27 at r3 (raw file):

Previously, knz (kena) wrote…

Nah:

  1. the metric objects are actually shared by all constructors at the same level
  2. they must be registered via a call to registry.AddMetricStruct() which exists only in the server initialization code. Plumbing the registry to every monitor would be a nightmare.
ok

pkg/sql/row_container.go, line 71 at r7 (raw file):

// memory growth.
func (p *planner) NewRowContainer(
  sessionBound bool, h ResultColumns, rowCapacity int,

this new argument definitely needs some explanation :)

But... but... why not take a WrappedAccount, and let the caller worry about it.

In fact, it bothers me that the RowContainer has a reference to a planner. I don't see a good reason why something as dumb as a "container" has to know anything about a planner, or a session. Just give it an account on which it can perform all the operations it needs. Trade one pointer for another.


pkg/sql/row_container.go, line 105 at r7 (raw file):

  c.rows = nil
  c.varSizedColumns = nil
  var acc WrappedMemoryAccount

wait, what's going on here? We're making a WrappedAcc just so we can close it? Can't you use the correct monitor directly to close it? Or, better yet, as I'm saying above, I hope c.memAcc can be wrapped by a higher level.


pkg/sql/session.go, line 97 at r7 (raw file):

  // It is not directly used, but rather indirectly used via
  // sessionMon and txnMon. sessionMon tracks session-bound objects
  // like prepared statements and result sets. txnMon tracks txn-bound objects.

... like the running state of the planNodes in the midst of performing computation.


pkg/sql/session.go, line 99 at r7 (raw file):

  // like prepared statements and result sets. txnMon tracks txn-bound objects.
  mon        mon.MemoryMonitor
  sessionMon mon.MemoryMonitor

why were sessionMon and txnMon split? They don't seem to act as buffers. Is it just so that we get a chance to check for leaks after every transaction? If that is the case, maybe we actually want a per-statement monitor? Or maybe it has something to do with the metrics we want to export? In any case, I think it deserves a comment.


pkg/sql/session.go, line 100 at r7 (raw file):

  mon        mon.MemoryMonitor
  sessionMon mon.MemoryMonitor
  txnMon     mon.MemoryMonitor

should txnMon live in TxnState ?


pkg/sql/session.go, line 318 at r7 (raw file):

      s.memMetrics.TxnCurBytesCount,
      s.memMetrics.TxnMaxBytesHist,
      &s.mon, mon.BoundAccount{}, 1)

how come we're not using a larger increment? I think there might be some benefit to buffering in this txn-mon, even if the parent monitor is not contended.


pkg/sql/session_mem_usage.go, line 40 at r7 (raw file):

// WrappableMemoryAccount encapsulates a MemoryAccount to
// give it the W() method below.

the comment is stale, there's no W() any more.

But... but... what the hell is a WrappableMemoryAccount? :)
It seems to me that the W...() funs should be on the Session, and then we can get rid of this mind-bend.


pkg/sql/session_mem_usage.go, line 100 at r7 (raw file):

}

// StartMonitor interfaces between Session and mon.MemoryUsageMonitor

s/ MemUsageMon/MemMon


pkg/sql/session_mem_usage.go, line 105 at r7 (raw file):

      s.memMetrics.CurBytesCount,
      s.memMetrics.MaxBytesHist,
      pool, reserved, -1)

I think maybe a comment is warranted here... So we pass reserved to s.mon where it acts as a buffer, but we're not giving it to either sessionMon or txnMon. So those guys don't start with any buffer, so they'll need to ask their parents for memory immediately. But that's cool, cause the session is single threaded, and the point of buffering is avoiding contention.


pkg/sql/mon/bound_account.go, line 15 at r7 (raw file):

// permissions and limitations under the License.
//
// Author: Raphael 'kena' Poss (knz@cockroachlabs.com)

i'd put this in mem_usage.go, next to MemoryAccount, for discoverability.
Particularly since it adds a method to MemoryMonitor.


pkg/sql/mon/bound_account.go, line 39 at r7 (raw file):

func (mm *MemoryMonitor) MakeBoundAccount(
  ctx context.Context, initialAllocation int64,
) (res BoundAccount, err error) {

"we" generally dislike named ret vals, and here you're not using them other than as declarations


pkg/sql/mon/bound_account.go, line 49 at r7 (raw file):

// Close is an accessor for b.mon.CloseAccount.
func (b *BoundAccount) Close(ctx context.Context) {
  if b.mon != nil {

I think it's worth explaining why this can be nil - it's only nil for accounts used to initialize top-level monitors, which have basically gotten memory out of the ether.

... later - I just saw the MakeStandaloneBudget above. Maybe the comment belongs there, and a reference to that here :)


pkg/sql/mon/mem_usage.go, line 238 at r3 (raw file):

Previously, knz (kena) wrote…

Yes but no.
The monitor is embedded in session so you would need to do session.txnMon = mon.MakeMonitor(...) at the start of every transaction, but this is invalid because the monitor contains a mutex.
I did rename the method as suggested though.

I think the assignment you say is invalid is no longer invalid, as of recently! You can `return` a `noCopy` object and `go vet` will let it slide! Move semantics! Basically, C++, except awful. If it works, I think it's a worthwhile cleanup.

pkg/sql/mon/mem_usage.go, line 204 at r7 (raw file):

  // monitor. The reserved bytes are released to their owner monitor
  // upon Stop.
  reserved BoundAccount

ah, I feel at peace now. If I had a cigar, I'd put my feet on the table and smoke it.


pkg/sql/pgwire/server.go, line 116 at r7 (raw file):

      server.metrics.SQLMemMetrics.CurBytesCount,
      server.metrics.SQLMemMetrics.MaxBytesHist,
      nil, mon.MakeStandaloneBudget(maxSQLMem), 1)

0, or -1, instead of 1, to signal that the value is funky (it's not used by this pool, is it)?


Comments from Reviewable

@knz knz force-pushed the mem-pool branch 3 times, most recently from 9ee3695 to 94b9b30 Compare October 30, 2016 01:01
@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 30, 2016

Indeed I moved the detailed plot to "advanced".


Review status: 0 of 40 files reviewed at latest revision, 21 unresolved discussions.


pkg/cli/interactive_tests/test_sql_mem_monitor.tcl, line 1 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

it's really nice that this works!

I was hoping we'd write this as an acceptance test. tcl is... exotic and creeping up in scope :)
In any case, the test deserves a comment about what it tests and what it'll do.

Done.

pkg/cli/interactive_tests/test_sql_mem_monitor.tcl, line 52 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

is this an outer join?

huh that's the regular short hand cross join.

pkg/cli/interactive_tests/test_sql_mem_monitor.tcl, line 66 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

ah, see, this is the crux of it, I think: I think part of the point of this test (or, rather, the point of all your work in general) has to be that the user should be able to set a limit that has something to do with the ulimit (ideally, exactly the ulimit), and our server is guaranteed to not crash. Like, it's "easy" to not crash if you refuse every query; the difficulty is in running close to your limit (so, having good utilization of the "hardware") and not crashing. Living on the edge.
I guess it's too early for that, since the rocksdb memory is not integrated with this your flag at all (although it does have its own flags), but at least aspirationally that's the dream, right? So maybe add a TODO here.

We discussed this face to face. I plan to clarify this issue specifically in an upcoming blog post.

pkg/sql/row_container.go, line 71 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

this new argument definitely needs some explanation :)

But... but... why not take a WrappedAccount, and let the caller worry about it.

In fact, it bothers me that the RowContainer has a reference to a planner. I don't see a good reason why something as dumb as a "container" has to know anything about a planner, or a session. Just give it an account on which it can perform all the operations it needs. Trade one pointer for another.

Done.

pkg/sql/row_container.go, line 105 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

wait, what's going on here? We're making a WrappedAcc just so we can close it? Can't you use the correct monitor directly to close it? Or, better yet, as I'm saying above, I hope c.memAcc can be wrapped by a higher level.

Done.

pkg/sql/session.go, line 97 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

... like the running state of the planNodes in the midst of performing computation.

Done.

pkg/sql/session.go, line 99 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

why were sessionMon and txnMon split? They don't seem to act as buffers. Is it just so that we get a chance to check for leaks after every transaction? If that is the case, maybe we actually want a per-statement monitor? Or maybe it has something to do with the metrics we want to export? In any case, I think it deserves a comment.

Per statement is certainly possible but probably rather useless. What we'll probably end up implementing is a monitor per planNode! So that we can get statistics for the individual stages of a SQL computation. However that's a bit further down the line.

For now the split is for a different reason, which is now documented in a comment.


pkg/sql/session.go, line 100 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

should txnMon live in TxnState ?

No it can't because TxnState is reset fully in `resetForNewSQLTxn` (`*ts = txnState{}`) and that would erase the monitor settings.

pkg/sql/session.go, line 318 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

how come we're not using a larger increment? I think there might be some benefit to buffering in this txn-mon, even if the parent monitor is not contended.

Yes absolutely, this was an oversight on my part. Changed.

pkg/sql/session_mem_usage.go, line 40 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

the comment is stale, there's no W() any more.

But... but... what the hell is a WrappableMemoryAccount? :)
It seems to me that the W...() funs should be on the Session, and then we can get rid of this mind-bend.

This does seem wise? The convenience syntax is `acc.W(session).AccountMethod()`. What you propose would yield `session.Wrap(acc).AccountMethod()` which I'm not sure is better.

pkg/sql/session_mem_usage.go, line 100 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

s/ MemUsageMon/MemMon

Done.

pkg/sql/session_mem_usage.go, line 105 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I think maybe a comment is warranted here... So we pass reserved to s.mon where it acts as a buffer, but we're not giving it to either sessionMon or txnMon. So those guys don't start with any buffer, so they'll need to ask their parents for memory immediately. But that's cool, cause the session is single threaded, and the point of buffering is avoiding contention.

Done.

pkg/sql/mon/mem_usage.go, line 238 at r3 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I think the assignment you say is invalid is no longer invalid, as of recently!
You can return a noCopy object and go vet will let it slide! Move semantics! Basically, C++, except awful.
If it works, I think it's a worthwhile cleanup.

Done.

pkg/sql/mon/mem_usage.go, line 204 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

ah, I feel at peace now. If I had a cigar, I'd put my feet on the table and smoke it.

😏

pkg/sql/pgwire/server.go, line 116 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

0, or -1, instead of 1, to signal that the value is funky (it's not used by this pool, is it)?

Done.

pkg/sql/mon/bound_account.go, line 15 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

i'd put this in mem_usage.go, next to MemoryAccount, for discoverability.
Particularly since it adds a method to MemoryMonitor.

Done.

pkg/sql/mon/bound_account.go, line 39 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

"we" generally dislike named ret vals, and here you're not using them other than as declarations

Done.

pkg/sql/mon/bound_account.go, line 49 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

I think it's worth explaining why this can be nil - it's only nil for accounts used to initialize top-level monitors, which have basically gotten memory out of the ether.

... later - I just saw the MakeStandaloneBudget above. Maybe the comment belongs there, and a reference to that here :)

Done.

Comments from Reviewable

@andreimatei
Copy link
Copy Markdown
Contributor

:lgtm:

🚢 it


Review status: 0 of 40 files reviewed at latest revision, 7 unresolved discussions, some commit checks failed.


pkg/sql/row_container.go, line 71 at r7 (raw file):

Previously, knz (kena) wrote…

Done.

well you haven't commented the parameter. I still think that the caller should be in charge of creating the `BoundAccount`.

pkg/sql/session.go, line 100 at r7 (raw file):

Previously, knz (kena) wrote…

No it can't because TxnState is reset fully in resetForNewSQLTxn (*ts = txnState{}) and that would erase the monitor settings.

well you could add a `txnState.Reset()`...

pkg/sql/session.go, line 96 at r9 (raw file):

  // mon tracks memory usage for SQL activity within this session. It
  // is not directly used, but rather indirectly used via sessionMon
  // and TxnState.mon. sessionMon tracks session-bound objects like prepared

there's no TxnState.mon. Although I still think there should be :)


pkg/sql/session_mem_usage.go, line 40 at r7 (raw file):

Previously, knz (kena) wrote…

This does seem wise? The convenience syntax is acc.W(session).AccountMethod(). What you propose would yield session.Wrap(acc).AccountMethod() which I'm not sure is better.

well there's so many types of accounts... I think it's worth getting rid of `WrappableAccount`. Your snippet glosses over how `acc` was created, which probably has some complexity compared to a simple `MemAccount`.

Comments from Reviewable

knz added 2 commits October 31, 2016 21:11
…eeds.

This patch does what it says on the cover.

It also introduces a separate method `handleAuthentication()` on the
v3conn object to deal with authentication. This method should
overwrite the User in SessionArgs if the protocol exchange decides a
new username.

As an added benefit this patch also tags logging messages with the
authenticated user.
… queries.

Prior to this patch, any query that required rows to be loaded in
server memory prior to being sent back to the client could cause the
server to crash if there is insufficient RAM to store all rows.

This patch builds upon the `MemoryUsageMonitor` abstraction (now
renamed to `MemoryMonitor`) by enabling chaining/stacking of monitors
upon each other.

A single `MemoryMonitor` object is shared by all client connections over
pgwire. Its capacity for a given node is 25% of total node RAM size,
unless overridden via `--max-sql-memory` on that node's `start` command
line.

Each client connection starts with a budget of 21KiB (configurable via
the env. var. `COCKROACH_BASE_SQL_MEMORY_BUDGET`). This budget is
pre-reserved from the common pool by the pgwire server (using a
pgwire-wide `MemoryMonitor`), and reused when a connection is closed
without interacting with the pool. This ensures that the pool is not
reached at all for what we expect is common usage: many clients using
only OLTP queries returning very few rows (or none) and without need
for dynamic server-side row storage.

Once a client connection exceeds its pre-reserved budget, its
session-bound "root" `MemoryMonitor` starts interacting with the pool
by requesting fixed-size chunks. These chunks are 10KiB by default,
and this size is configurable via the
env. var. `COCKROACH_MEMORY_ALLOCATION_CHUNK_SIZE`. If a session is
long-lived, a monitor will relinquish unused memory to the common pool
when there are more than 10 unused reserved chunks (this factor is
configurable via `COCKROACH_MAX_ALLOCATED_UNUSED_BLOCKS`).

Within a session two sub-monitors further detail allocations.  The
"session monitor" tracks allocations that can span an entire session,
for example result sets and prepared statements. The "txn monitor"
tracks allocations within transactions.

A separate stack of `MemoryMonitor` objects supports SQL allocations
by the `adminServer` for UI API calls and admin commands; this is
currently unbounded.

Another separate stack of monitors also supports allocations by the
internal executor (SQL leases, log events, etc.), also unbounded.

The various monitor statistics are also aggregated in the admin UI.

Finally, the monitors also report to the log files when the total
allocation sizes exceeds some threshold. The thresholds are
configurable via env vars:

- COCKROACH_NOTEWORTHY_SQL_MEMORY_USAGE, default 100MiB
  Overall SQL allocations by clients.

- COCKROACH_NOTEWORTHY_SESSION_MEMORY_USAGE, default 10KiB
  Allocations by either the session-wide or txn monitor in each
  session.

- COCKROACH_NOTEWORTHY_CONN_MEMORY_USAGE, default 2MiB
  Upfront reservations by pgwire.

- COCKROACH_NOTEWORTHY_INTERNAL_MEMORY_USAGE, default 100KiB
  Allocations for internal operations (leases, etc.)

- COCKROACH_NOTEWORTHY_ADMIN_MEMORY_USAGE, default 100KiB
  Allocations for queries by the admin UI.
@knz
Copy link
Copy Markdown
Contributor Author

knz commented Oct 31, 2016

Review status: 0 of 40 files reviewed at latest revision, 7 unresolved discussions, some commit checks pending.


pkg/sql/row_container.go, line 71 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

well you haven't commented the parameter. I still think that the caller should be in charge of creating the BoundAccount.

Oh I had missed your previous comment actually. I did what you suggested now.

pkg/sql/session.go, line 100 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

well you could add a txnState.Reset()...

Done (in a different way, but still, it was a good idea).

pkg/sql/session.go, line 96 at r9 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

there's no TxnState.mon. Although I still think there should be :)

Done.

pkg/sql/session_mem_usage.go, line 40 at r7 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

well there's so many types of accounts... I think it's worth getting rid of WrappableAccount. Your snippet glosses over how acc was created, which probably has some complexity compared to a simple MemAccount.

We've discussed that before -- the code becomes too complex when these wrappable accounts disappear.

Comments from Reviewable

@knz knz added S-1-stability Severe stability issues that can be fixed by upgrading, but usually don’t resolve by restarting and removed S-1-stability Severe stability issues that can be fixed by upgrading, but usually don’t resolve by restarting labels Oct 31, 2016
@knz knz merged commit abf2f2f into cockroachdb:master Oct 31, 2016
@knz knz deleted the mem-pool branch October 31, 2016 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sql: implicit cross joins can cause OOM errors

6 participants