Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1eunex-0001z8-0R for pgsql-hackers@arkaria.postgresql.org; Sat, 10 Mar 2018 23:12:15 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1euneu-0005xL-Vi for pgsql-hackers@arkaria.postgresql.org; Sat, 10 Mar 2018 23:12:12 +0000 Received: from makus.postgresql.org ([2001:4800:1501:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1euneu-0005x7-Ky for pgsql-hackers@lists.postgresql.org; Sat, 10 Mar 2018 23:12:12 +0000 Received: from mail-lf0-x22a.google.com ([2a00:1450:4010:c07::22a]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1eunem-0007UY-Jl for pgsql-hackers@postgresql.org; Sat, 10 Mar 2018 23:12:11 +0000 Received: by mail-lf0-x22a.google.com with SMTP id f75-v6so18059710lfg.6 for ; Sat, 10 Mar 2018 15:12:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=subject:to:cc:references:from:message-id:date:user-agent :mime-version:in-reply-to:content-language:content-transfer-encoding; bh=H8nm2KzoiDFFX08CUD1e9ILXDjBiKR8I6+O0tcqScQA=; b=b9YCf+PU2t9dPEoDE6r+CfWlWRtIGQQiOp/VF13cYX8ygvTzcvol4d2Q3VctQXkamx ILwHsVuFlngju4M3adYnaRXURn4e9TTi1h6WekO9rUpG7cw9626QgM9P0sxHtRht5wd2 k9phDAWSMIPmvFxtBQytCRZHiXSNo1lgniKS41Cu/aQnD6utdC0E9PBnQXCOjJcfJY9J 7sfO+CK2kWNYBeMM7KV0i9yiApmO7BmP9jyYuBjHm7qgSJd1ZI4k+sxn4ANxxo+j/blF +wgnkYM7D9EgfT0LYiNI+dJY97VEpzG9KNj5ZzWQQ85YScillhurxkSWUs7bZdIi7mZt g1mg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:to:cc:references:from:message-id:date :user-agent:mime-version:in-reply-to:content-language :content-transfer-encoding; bh=H8nm2KzoiDFFX08CUD1e9ILXDjBiKR8I6+O0tcqScQA=; b=CGVvCJx9S5FvfShvD6M7EXc/ctxak0fiKjLrzpLinf8e50XFX9TCHyICjMmYhuQqo6 3L4a+sqwx9Q0W66ERLIB/iGxKhIOQDWc1ECmfV2Kn3cUDHZr+B7V/SupZzE1a1c6cdUO /Qtt4d0SFKTo4tejmMjGHjKH1uiMAVLPPhzKckUfFCKhoCh6Gw+RvcN81YhRfPxvAYMv PNZ/f4zwrbIJG22vI4Kaijm+grKrwtSBkbY194myyjQldXHZQSBxU3H5PV+YPlONaNHY sj0eZJSsOvZZhd7wJqDdqk69YW/dbKRfEvXOXULOWiZFpRWBfvsFzVx3xraEoHFNPTZ0 9qjA== X-Gm-Message-State: AElRT7GTyDvBTUKXSzajx2kT12hnDJhaZLmfslB7nOdUmKU1T1ruPPUV wsCD3bjl29SqkETgH13hRbGmpSm9Serrpa+8bEWek14GvRdMbo4DUdelec2aln9br9XMovGxJdI vZXSAEjItcxnC7oLcQQh1WoXw5xjS/DKB33TEMwwikjC2d6uysKzH8x9FK4HvsTu33pBxH2UBmJ uwAQ3sf0KC4/c= X-Google-Smtp-Source: AG47ELtfGtQVFvP9KrFzR5e/aW8IdiO6zCFk4L2CclSKhOx/BB4TrP25fpskoZYNme5Tllvcjnzw0A== X-Received: by 10.46.73.73 with SMTP id b9mr2057898ljd.129.1520723520276; Sat, 10 Mar 2018 15:12:00 -0800 (PST) Received: from [10.137.2.17] (178-78-220-132.customers.ownit.se. [178.78.220.132]) by smtp.gmail.com with ESMTPSA id t28-v6sm967370lfd.71.2018.03.10.15.11.59 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 10 Mar 2018 15:11:59 -0800 (PST) Subject: Re: Parallel Aggregates for string_agg and array_agg To: David Rowley Cc: Andres Freund , PostgreSQL-development References: <20180301212631.kede4nyuttzxwoxq@alap3.anarazel.de> From: Tomas Vondra Message-ID: <24abe018-b3c6-2117-b5b9-39e660db00bd@2ndquadrant.com> Date: Sun, 11 Mar 2018 00:11:57 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.4.0 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk On 03/05/2018 04:51 AM, David Rowley wrote: > On 5 March 2018 at 04:54, Tomas Vondra wrote: >> 1) There seems to be forgotten declaration of initArrayResultInternal in >> arrayfuncs.c. I suppose you've renamed it to initArrayResultWithSize and >> moved it to a header file, and forgotten to remove this bit. > > Oops. Must be left over from trying things another way. Removed. > >> 2) bytea_string_agg_deserialize has this comment: >> >> /* >> * data: technically we could reuse the buf.data buffer, but that is >> * slightly larger than we need due to the extra 4 bytes for the cursor >> */ >> >> I find the argument "it has 4 extra bytes, so we'll allocate new chunk" >> somewhat weak. We do allocate the memory in 2^n chunks anyway, so the >> new chunk is likely to be much larger anyway. I'm not saying we need to >> reuse the buffer, IMHO the savings will be non-measurable. > > Agreed. I've removed that part of the comment. > >> 3) string_agg_transfn and bytea_string_agg_transfn say this" >> >> /* >> * We always append the delimiter text before the value text, even >> * with the first aggregated item. The reason for this is that if >> * this state needs to be combined with another state using the >> * aggregate's combine function, then we need to have the delimiter >> * for the first aggregated item. The first delimiter will be >> * stripped off in the final function anyway. We use a little cheat >> * here and store the position of the actual first value (after the >> * delimiter) using the StringInfo's cursor variable. This relies on >> * the cursor not being used for any other purpose. >> */ >> >> How does this make the code any simpler, compared to not adding the >> delimiter (as before) and adding it when combining the values (at which >> point you should know when it's actually needed)? > > The problem is that if we don't record the first delimiter then we > won't know what it is when it comes to combining the states. > > Consider the following slightly backward-looking case; > > select string_agg(',', x::text) from generate_Series(1,10)x; > string_agg > ---------------------- > ,2,3,4,5,6,7,8,9,10, > > Here the delimiter is the number, not the ','. Of course, the > delimiter for the first aggregated result is not shown. The previous > implementation of the transfn for this aggregate just threw away the > first delimiter, but that's no good for parallel aggregates as the > transfn may be getting called in a parallel worker, in which case > we'll need that delimiter in the combine function to properly join to > the other partial aggregated results matching the same group. > Hmmm, you're right I haven't considered the delimiter might be variable. But isn't just yet another hint that while StringInfo was suitable for aggregate state of non-parallel string_agg, it may not be for the parallel version? What if the parallel string_agg instead used something like this: struct StringAggState { char *delimiter; /* first delimiter */ StringInfoData str; /* partial aggregate state */ } StringAggState; I think that would make the code cleaner, compared to using the cursor field for that purpose. But maybe it'd make it not possible to share code with the non-parallel aggregate, not sure. I always considered the cursor field to be meant for scanning through StringInfo, so using it for this purpose seems a bit like a misuse. > Probably I could improve the comment a bit. I understand that having a > variable delimiter is not commonplace in the normal world. I certainly > had never considered it before working on this. I scratched my head a > bit when doing this and thought about inventing a new trans type, but > I decided that the most efficient design for an aggregate state was to > store the delimiter and data all in one buffer and have a pointer to > the start of the actual data... StringInfo has exactly what's required > if you use the cursor as the pointer, so I didn't go and reinvent the > wheel. > I wonder why the variable delimiter is not mentioned in the docs ... (at least I haven't found anything mentioning the behavior). > I've now changed the comment to read: > > /* > * It's important that we store the delimiter text for all aggregated > * items, even the first one, which at first thought you might think > * could just be discarded. The reason for this is that if this > * function is being called from a parallel worker, then we'll need > * the first delimiter in order to properly combine the partially > * aggregated state with the states coming from other workers. In the > * final output, the first delimiter will be stripped off of the final > * aggregate state, but in order to know where the actual first data > * is we must store the position of the first data value somewhere. > * Conveniently, StringInfo has a cursor property which can be used > * to serve our needs here. > */ > > I've also updated an incorrect Assert in array_agg_array_serialize. > > Please find attached the updated patch. > Seems fine to me. I plan to do a bit more testing/review next week, but I plan to move it to RFC after that (unless I run into something during the review, but I don't expect that). regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services