On 01/03/2023 09:33, Andres Freund wrote:
On 2023-02-21 17:33:31 +0100, Alvaro Herrera wrote:
On 2023-Feb-21, Heikki Linnakangas wrote:

+static BlockNumber
+BulkExtendSharedRelationBuffered(Relation rel,
+                                                                SMgrRelation 
smgr,
+                                                                bool 
skip_extension_lock,
+                                                                char 
relpersistence,
+                                                                ForkNumber 
fork, ReadBufferMode mode,
+                                                                
BufferAccessStrategy strategy,
+                                                                uint32 
*num_pages,
+                                                                uint32 
num_locked_pages,
+                                                                Buffer 
*buffers)

Ugh, that's a lot of arguments, some are inputs and some are outputs. I
don't have any concrete suggestions, but could we simplify this somehow?
Needs a comment at least.

Yeah, I noticed this too.  I think it would be easy enough to add a new
struct that can be passed as a pointer, which can be stack-allocated
by the caller, and which holds the input arguments that are common to
both functions, as is sensible.

I played a fair bit with various options. I ended up not using a struct to
pass most options, but instead go for a flags argument. However, I did use a
struct for passing either relation or smgr.


typedef enum ExtendBufferedFlags {
        EB_SKIP_EXTENSION_LOCK = (1 << 0),
        EB_IN_RECOVERY = (1 << 1),
        EB_CREATE_FORK_IF_NEEDED = (1 << 2),
        EB_LOCK_FIRST = (1 << 3),

        /* internal flags follow */
        EB_RELEASE_PINS = (1 << 4),
} ExtendBufferedFlags;

Is EB_IN_RECOVERY always set when RecoveryInProgress()? Is it really needed? What does EB_LOCK_FIRST do?

/*
  * To identify the relation - either relation or smgr + relpersistence has to
  * be specified. Used via the EB_REL()/EB_SMGR() macros below. This allows us
  * to use the same function for both crash recovery and normal operation.
  */
typedef struct ExtendBufferedWhat
{
        Relation rel;
        struct SMgrRelationData *smgr;
        char relpersistence;
} ExtendBufferedWhat;

#define EB_REL(p_rel) ((ExtendBufferedWhat){.rel = p_rel})
/* requires use of EB_SKIP_EXTENSION_LOCK */
#define EB_SMGR(p_smgr, p_relpersistence) ((ExtendBufferedWhat){.smgr = p_smgr, 
.relpersistence = p_relpersistence})

Clever. I'm still not 100% convinced we need the EB_SMGR variant, but with this we'll have the flexibility in any case.

extern Buffer ExtendBufferedRel(ExtendBufferedWhat eb,
                                                                ForkNumber 
forkNum,
                                                                
BufferAccessStrategy strategy,
                                                                uint32 flags);
extern BlockNumber ExtendBufferedRelBy(ExtendBufferedWhat eb,
                                                                           
ForkNumber fork,
                                                                           
BufferAccessStrategy strategy,
                                                                           
uint32 flags,
                                                                           
uint32 extend_by,
                                                                           
Buffer *buffers,
                                                                           
uint32 *extended_by);
extern Buffer ExtendBufferedRelTo(ExtendBufferedWhat eb,
                                                                  ForkNumber 
fork,
                                                                  
BufferAccessStrategy strategy,
                                                                  uint32 flags,
                                                                  BlockNumber 
extend_to,
                                                                  
ReadBufferMode mode);

As you can see I removed ReadBufferMode from most of the functions (as
suggested by Heikki earlier). When extending by 1/multiple pages, we only need
to know whether to lock or not.

Ok, that's better. Still complex and a lot of arguments, but I don't have any great suggestions on how to improve it.

The reason ExtendBufferedRelTo() has a 'mode' argument is that that allows to
fall back to reading page normally if there was a concurrent relation
extension.

Hmm, I think you'll need another return value, to let the caller know if the relation was extended or not. Or a flag to ereport(ERROR) if the page already exists, for ginbuild() and friends.

The reason EB_CREATE_FORK_IF_NEEDED exists is to remove the duplicated,
gnarly, code to do so from vm_extend(), fsm_extend().

Makes sense.

I'm not sure about the function naming pattern. I do like 'By' a lot more than
the Bulk prefix I used before.

+1

- Heikki



Reply via email to