Chunk based decompression and recompressing back for entire table

Hi @jonatasdp ,
Feel free to add or correct if any correction required.

I have developed a SQL script to perform chunk-wise decompression and recompression. I thought it might be helpful for others who face similar situations that require changing the primary constraint on a table :slight_smile:

DO $$
DECLARE
    originally_uncompressed TEXT[ ] := ARRAY[]::TEXT[]; -- chunks that were uncompressed at start
    originally_compressed   TEXT[] := ARRAY[]::TEXT[]; -- chunks that were compressed at start
    decompressed_chunks     TEXT[] := ARRAY[]::TEXT[]; -- chunks we successfully decompressed
    recompressed_chunks     TEXT[] := ARRAY[]::TEXT[]; -- chunks we successfully recompressed
    rec RECORD;
BEGIN
    -- Step 1: Record original state
    FOR rec IN
        SELECT chunk_name, is_compressed
        FROM timescaledb_information.chunks
        WHERE hypertable_name = 'mytable'
    LOOP
        IF rec.is_compressed THEN
            originally_compressed := array_append(originally_compressed, rec.chunk_name);
        ELSE
            originally_uncompressed := array_append(originally_uncompressed, rec.chunk_name);
        END IF;
    END LOOP;

    -- Step 2: Try decompressing only originally compressed chunks
    FOR rec IN SELECT unnest(originally_compressed) AS chunk_name
    LOOP
        BEGIN
            RAISE NOTICE 'Decompressing: _timescaledb_internal.%', rec.chunk_name;
            EXECUTE format('SELECT decompress_chunk(%L);', '_timescaledb_internal.' || rec.chunk_name);
            decompressed_chunks := array_append(decompressed_chunks, rec.chunk_name);
        EXCEPTION WHEN OTHERS THEN
            RAISE WARNING 'Decompression failed on chunk: _timescaledb_internal.%', rec.chunk_name;

            -- ROLLBACK: Re-compress already decompressed chunks
            FOREACH rec.chunk_name IN ARRAY decompressed_chunks
            LOOP
                BEGIN
                    RAISE NOTICE 'Rollback: recompressing chunk: %', rec.chunk_name;
                    EXECUTE format('SELECT compress_chunk(%L);', '_timescaledb_internal.' || rec.chunk_name);
                EXCEPTION WHEN OTHERS THEN
                    RAISE WARNING 'Failed to rollback chunk: %', rec.chunk_name;
                END;
            END LOOP;

            RAISE EXCEPTION 'Aborting due to decompression failure.';
        END;
    END LOOP;

    -- You can do your processing here
    RAISE NOTICE 'Performing custom logic here...';
    -- Change on primary key constraint or other operation where decompression required;

    -- Step 3: Try recompressing only originally compressed chunks
    FOR rec IN SELECT unnest(originally_compressed) AS chunk_name
    LOOP
        BEGIN
            RAISE NOTICE 'Compressing: %', rec.chunk_name;
            EXECUTE format('SELECT compress_chunk(%I.%I);', '_timescaledb_internal', rec.chunk_name);
            recompressed_chunks := array_append(recompressed_chunks, rec.chunk_name);
        EXCEPTION WHEN OTHERS THEN
            RAISE WARNING 'Compression failed on chunk: %', rec.chunk_name;

            -- ROLLBACK: Decompress the chunks we re-compressed
            FOREACH rec.chunk_name IN ARRAY recompressed_chunks
            LOOP
                BEGIN
                    RAISE NOTICE 'Rollback: decompressing chunk: %', rec.chunk_name;
                    EXECUTE format('SELECT decompress_chunk(%L);', '_timescaledb_internal.' || rec.chunk_name);
                EXCEPTION WHEN OTHERS THEN
                    RAISE WARNING 'Failed to rollback chunk: %', rec.chunk_name;
                END;
            END LOOP;

            -- Also re-compress chunks that were decompressed earlier but not yet recompressed
            FOREACH rec.chunk_name IN ARRAY decompressed_chunks
            LOOP
                IF NOT rec.chunk_name = ANY(recompressed_chunks) THEN
                    BEGIN
                        RAISE NOTICE 'Rollback: compressing chunk: %', rec.chunk_name;
						EXECUTE format('SELECT compress_chunk(%L);', '_timescaledb_internal.' || rec.chunk_name);
                    EXCEPTION WHEN OTHERS THEN
                        RAISE WARNING 'Failed to rollback chunk: %', rec.chunk_name;
                    END;
                END IF;
            END LOOP;

            RAISE EXCEPTION 'Aborting due to compression failure.';
        END;
    END LOOP;

    RAISE NOTICE 'Operations completed successfully.';
END $$;

Another approach involves decompressing and then recompressing all chunks, which can cause significant delays when dealing with many chunks, preventing end users from receiving updates during this process

PERFORM compress_chunk(c, true) FROM show_chunks('mytable') c;
PERFORM decompress_chunk(c, true) FROM show_chunks('mytable') c;