All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.questdb.griffin.PurgingOperator Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2022 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.griffin;

import io.questdb.MessageBus;
import io.questdb.cairo.*;
import io.questdb.cairo.sql.TableRecordMetadata;
import io.questdb.log.Log;
import io.questdb.mp.Sequence;
import io.questdb.std.FilesFacade;
import io.questdb.std.IntList;
import io.questdb.std.LongList;
import io.questdb.std.Os;
import io.questdb.std.str.Path;
import io.questdb.tasks.ColumnPurgeTask;

public abstract class PurgingOperator {
    protected final LongList cleanupColumnVersions = new LongList();
    protected final CairoConfiguration configuration;
    protected final FilesFacade ff;
    protected final MessageBus messageBus;
    protected final Path path;
    protected final int rootLen;
    protected final TableWriter tableWriter;
    protected final IntList updateColumnIndexes = new IntList();
    private final LongList cleanupColumnVersionsAsync = new LongList();
    private final Log log;

    protected PurgingOperator(
            Log log,
            CairoConfiguration configuration,
            MessageBus messageBus,
            TableWriter tableWriter,
            Path path,
            int rootLen
    ) {
        this.log = log;
        this.configuration = configuration;
        this.messageBus = messageBus;
        this.ff = configuration.getFilesFacade();
        this.tableWriter = tableWriter;
        this.path = path;
        this.rootLen = rootLen;
    }

    public void purgeOldColumnVersions() {
        boolean anyReadersBeforeCommittedTxn = tableWriter.checkScoreboardHasReadersBeforeLastCommittedTxn();
        TableRecordMetadata tableMetadata = tableWriter.getMetadata();

        path.trimTo(rootLen);
        long updateTxn = tableWriter.getTxn();

        try {
            // Process updated column by column, one at the time
            for (int updatedCol = 0, nn = updateColumnIndexes.size(); updatedCol < nn; updatedCol++) {
                int processColumnIndex = updateColumnIndexes.getQuick(updatedCol);
                CharSequence columnName = tableMetadata.getColumnName(processColumnIndex);
                int columnType = tableMetadata.getColumnType(processColumnIndex);
                cleanupColumnVersionsAsync.clear();

                for (int i = 0, n = cleanupColumnVersions.size(); i < n; i += 4) {
                    int columnIndex = (int) cleanupColumnVersions.getQuick(i);
                    long columnVersion = cleanupColumnVersions.getQuick(i + 1);
                    long partitionTimestamp = cleanupColumnVersions.getQuick(i + 2);
                    long partitionNameTxn = cleanupColumnVersions.getQuick(i + 3);

                    // Process updated column by column, one at a time
                    if (columnIndex == processColumnIndex) {
                        boolean columnPurged = !anyReadersBeforeCommittedTxn;
                        if (!anyReadersBeforeCommittedTxn) {
                            path.trimTo(rootLen);
                            TableUtils.setPathForPartition(path, tableWriter.getPartitionBy(), partitionTimestamp, false);
                            TableUtils.txnPartitionConditionally(path, partitionNameTxn);
                            int pathPartitionLen = path.length();
                            TableUtils.dFile(path, columnName, columnVersion);
                            if (!ff.remove(path.$())) {
                                columnPurged = false;
                            }
                            if (columnPurged && ColumnType.isVariableLength(columnType)) {
                                path.trimTo(pathPartitionLen);
                                TableUtils.iFile(path, columnName, columnVersion);
                                TableUtils.iFile(path.$(), columnName, columnVersion);
                                if (!ff.remove(path.$()) && ff.exists(path)) {
                                    columnPurged = false;
                                }
                            }
                            if (columnPurged && tableMetadata.isColumnIndexed(columnIndex)) {
                                path.trimTo(pathPartitionLen);
                                BitmapIndexUtils.valueFileName(path, columnName, columnVersion);
                                if (!ff.remove(path.$()) && ff.exists(path)) {
                                    columnPurged = false;
                                }

                                path.trimTo(pathPartitionLen);
                                BitmapIndexUtils.keyFileName(path, columnName, columnVersion);
                                if (!ff.remove(path.$()) && ff.exists(path)) {
                                    columnPurged = false;
                                }
                            }
                        }

                        if (!columnPurged) {
                            cleanupColumnVersionsAsync.add(columnVersion, partitionTimestamp, partitionNameTxn, 0L);
                        }
                    }
                }

                // if anything not purged, schedule async purge
                if (cleanupColumnVersionsAsync.size() > 0) {
                    purgeColumnVersionAsync(
                            tableWriter.getTableToken(),
                            columnName,
                            tableMetadata.getTableId(),
                            (int) tableWriter.getTruncateVersion(),
                            columnType,
                            tableWriter.getPartitionBy(),
                            updateTxn,
                            cleanupColumnVersionsAsync
                    );
                    log.info().$("column purge scheduled [table=").utf8(tableWriter.getTableToken().getTableName())
                            .$(", column=").utf8(columnName)
                            .$(", updateTxn=").$(updateTxn)
                            .I$();
                } else {
                    log.info().$("columns purged locally [table=").utf8(tableWriter.getTableToken().getTableName())
                            .$(", column=").utf8(columnName)
                            .$(", newColumnVersion=").$(updateTxn - 1)
                            .I$();
                }
            }
        } finally {
            path.trimTo(rootLen);
        }
    }

    private void purgeColumnVersionAsync(
            TableToken tableName,
            CharSequence columnName,
            int tableId,
            int tableTruncateVersion,
            int columnType,
            int partitionBy,
            long updateTxn,
            LongList columnVersions
    ) {
        Sequence pubSeq = messageBus.getColumnPurgePubSeq();
        while (true) {
            long cursor = pubSeq.next();
            if (cursor > -1L) {
                ColumnPurgeTask task = messageBus.getColumnPurgeQueue().get(cursor);
                task.of(tableName, columnName, tableId, tableTruncateVersion, columnType, partitionBy, updateTxn, columnVersions);
                pubSeq.done(cursor);
                return;
            } else if (cursor == -1L) {
                // Queue overflow
                log.error().$("cannot schedule column purge, purge queue is full. Please run 'VACUUM TABLE \"").utf8(tableName.getTableName())
                        .$("\"' [columnName=").utf8(columnName)
                        .$(", updateTxn=").$(updateTxn)
                        .I$();
                return;
            }
            Os.pause();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy