Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rL43M-005uxb-Ep for pgsql-hackers@arkaria.postgresql.org; Wed, 03 Jan 2024 16:25:12 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1rL43K-006Uh7-Ox for pgsql-hackers@arkaria.postgresql.org; Wed, 03 Jan 2024 16:25:10 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rL43K-006Ugz-Ab for pgsql-hackers@lists.postgresql.org; Wed, 03 Jan 2024 16:25:10 +0000 Received: from relay3-d.mail.gandi.net ([217.70.183.195]) by magus.postgresql.org with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rL43H-00FMWG-9d for pgsql-hackers@postgresql.org; Wed, 03 Jan 2024 16:25:09 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 6F6F36000A; Wed, 3 Jan 2024 16:25:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=abcsql.com; s=gm1; t=1704299105; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=at5fBxK4RLhVg5DyOoxtY9s3CYBhm4T8miZ7JSt5fpU=; b=k2WYc7rlhGuW+zfFx+OFy7PjOEKOKqDYsDohwUw5XDpfo+NzIWCmIBEDOOylqp5UU5ikRJ CJ+A1/j4RDt5B3mIwPvddvJExNdgoK/8x+eebHQgIEXRHnI8K+K2KQkZ48+UhW9bFo0or5 NyirTSzdMfTX8zt3sQNkVMiY+lpxBfQrFMtQm/3oe2J5625kGAuCf3ytnLzcy76YWzVxhx AudwmZtPXtIpwqvJkVTh9Fv6iA6AvqvLwtErpHI1vYTaqyUN4+yTh94KhWA84A9NgHcaFu uUf/aRIEBl5UvS8A6SvsVoMLFX3w4JxBymksDQyiq9eJT5n4qz+WxbwGcfx2cg== Message-ID: <8b8c709a-c7cc-4965-8296-64f549c27501@abcsql.com> Date: Wed, 3 Jan 2024 17:25:03 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: =?UTF-8?Q?C=C3=A9dric_Villemain?= Subject: Re: Extension Enhancement: Buffer Invalidation in pg_buffercache To: Palak Chaturvedi , Nitin Jadhav , pgsql-hackers@postgresql.org, Thomas Munro References: Content-Language: en-US Organization: Data Bene In-Reply-To: Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-GND-Sasl: cedric.villemain@abcsql.com List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi Palak, I did a quick review of the patch: +CREATE FUNCTION pg_buffercache_invalidate(IN int, IN bool default true) +RETURNS bool +AS 'MODULE_PATHNAME', 'pg_buffercache_invalidate' +LANGUAGE C PARALLEL SAFE; --> Not enforced anywhere, but you can also add a comment to the function, for end users... +PG_FUNCTION_INFO_V1(pg_buffercache_invalidate); +Datum +pg_buffercache_invalidate(PG_FUNCTION_ARGS) +{ +    Buffer        bufnum; "Buffer blocknum" is not correct in this context I believe. Buffer is when you have to manage Local buffer too (negative number). Here uint32 is probably the good choice at the end, as used in pg_buffercache in other places. Also in this extension bufferid is used, not buffernum. +    bufnum = PG_GETARG_INT32(0); +    if (bufnum <= 0 || bufnum > NBuffers) maybe have a look at pageinspect and its PG_GETARG_UINT32. +    { +        ereport(ERROR, +                (errcode(ERRCODE_INVALID_PARAMETER_VALUE), +                 errmsg("buffernum is not valid"))); https://www.postgresql.org/docs/16/error-style-guide.html let me think that message like 'buffernum is not valid' can be enhanced: out of range, cannot be negative or exceed number of shared buffers.... ? Maybe add the value to the message. + +    } + +    /* +     * Check whether to force invalidate the dirty buffer. The default value of force is true. +     */ + +    force = PG_GETARG_BOOL(1); I think you also need to test PG_ARGISNULL with force parameter. +/* + * Try Invalidating a buffer using bufnum. + * If the buffer is invalid, the function returns false. + * The function checks for dirty buffer and flushes the dirty buffer before invalidating. + * If the buffer is still dirty it returns false. + */ +bool +TryInvalidateBuffer(Buffer bufnum, bool force) +{ +    BufferDesc *bufHdr = GetBufferDescriptor(bufnum - 1); this is not safe, GetBufferDescriptor() accepts uint, but can receive negative here. Use uint32 and bufferid. +    uint32        buf_state; +    ReservePrivateRefCountEntry(); + +    buf_state = LockBufHdr(bufHdr); +    if ((buf_state & BM_VALID) == BM_VALID) +    { +        /* +         * The buffer is pinned therefore cannot invalidate. +         */ +        if (BUF_STATE_GET_REFCOUNT(buf_state) > 0) +        { +            UnlockBufHdr(bufHdr, buf_state); +            return false; +        } +        if ((buf_state & BM_DIRTY) == BM_DIRTY) +        { +            /* +             * If the buffer is dirty and the user has not asked to clear the dirty buffer return false. +             * Otherwise clear the dirty buffer. +             */ +            if(!force){ +                return false; probably need to unlockbuffer here too. +            } +            /* +             * Try once to flush the dirty buffer. +             */ +            ResourceOwnerEnlargeBuffers(CurrentResourceOwner); +            PinBuffer_Locked(bufHdr); +            LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED); +            FlushBuffer(bufHdr, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL); + LWLockRelease(BufferDescriptorGetContentLock(bufHdr)); +            UnpinBuffer(bufHdr); I am unsure of this area (the code is correct, but I wonder why there is no static code for this part -from pin to unpin- in PostgreSQL), and maybe better to go with FlushOneBuffer() ? Also it is probably required to account for the shared buffer eviction in some pg_stat* view or table. Not sure how disk syncing is handled after this sequence nor if it's important ? +            buf_state = LockBufHdr(bufHdr); +            if (BUF_STATE_GET_REFCOUNT(buf_state) > 0) +            { +                UnlockBufHdr(bufHdr, buf_state); +                return false; +            } + +            /* +             * If its dirty again or not valid anymore give up. +             */ + +            if ((buf_state & (BM_DIRTY | BM_VALID)) != (BM_VALID)) +            { +                UnlockBufHdr(bufHdr, buf_state); +                return false; +            } + +        } + +        InvalidateBuffer(bufHdr); +        return true; +    } +    else +    { +        UnlockBufHdr(bufHdr, buf_state); +        return false; +    } Maybe safe to remove the else {} ... Maybe more tempting to start the big if with the following instead less nested...): +    if ((buf_state & BM_VALID) != BM_VALID) +    { +        UnlockBufHdr(bufHdr, buf_state); +        return false; +    } Doc and test are absent. --- Cédric Villemain +33 (0)6 20 30 22 52 https://Data-Bene.io PostgreSQL Expertise, Support, Training, R&D