Class ::nsmcp::AccessRecordPartitions (public)

 ::nx::Trait ::nsmcp::AccessRecordPartitions[i]

Defined in /usr/local/ns/tcl/nsmcp/log-traffic-record-01-partitions.tcl

Testcases:
No testcase defined.
Source code:
    :method partitionOpen {} {
        set stored [:stateGet partitionInterval 0]
        if {$stored!=${:partitionInterval} && ($stored>0 || ${:partitionInterval}>0)} {
            if {$stored>0} {
                error "record-store-partition-reset-required"
            }
            if {${:mode} ne "writer"} {
                error "record-store-partition-reset-required"
            }
            ${:db} transaction {
                # Check under the writer lock; never convert populated caches.
                if {[${:db} scalar {SELECT EXISTS(SELECT 1 FROM main.rows LIMIT 1)
                    OR EXISTS(SELECT 1 FROM main.files LIMIT 1)}]} {
                    error "record-store-partition-reset-required"
                }
                ${:db} execute {
                    ALTER TABLE main.rows RENAME TO row_template;
                    CREATE TABLE main.partitions(
                        start INTEGER PRIMARY KEY,stop INTEGER NOT NULL,name TEXT UNIQUE NOT NULL,
                        n INTEGER NOT NULL DEFAULT 0,first REAL,last REAL)
                }
                ${:db} execute {UPDATE main.meta SET version=4}
                :statePut partitionInterval ${:partitionInterval}
                :statePut rollupsReady 1
                set bootstrap [:dict getdef ${:settings} bootstrapSeconds 172800]
                :statePut bootstrapFrom [expr {[clock seconds]-min($bootstrap,[dict get ${:settings} retention])}]
                :partitionView
            }
        }
        if {${:partitionInterval}>0} {
            set sql [${:db} scalar {SELECT sql FROM main.sqlite_schema WHERE name='row_template' AND type='table'}]
            if {$sql eq {} || [string first ( $sql]<0} {
                error "record-store-partition-reset-required"
            }
            set :partitionDDL [string range $sql [string first ( $sql] end]
        }
    }

    :method partitionView {} {
        set sources [list {SELECT * FROM row_template WHERE 0}]
        set parts [${:db} execute {SELECT name FROM main.partitions ORDER BY start}]
        if {[llength $parts]>256} {
            error "record-store-partition-limit"
        }
        foreach part $parts {
            set name [dict get $part name]
            if {![regexp {^p_[0-9]+$} $name]} {
                error "record-store-invalid-partition"
            }
            lappend sources "SELECT * FROM $name"
        }
        ${:db} execute {DROP VIEW IF EXISTS main.rows}
        ${:db} execute "CREATE VIEW main.rows AS [join $sources { UNION ALL }]"
    }

    :method partitionEnsure {start} {
        set stop [expr {$start+${:partitionInterval}}]
        set name p_$start
        if {![${:db} scalar {SELECT EXISTS(SELECT 1 FROM main.partitions WHERE start=:start)}]} {
            if {[${:db} scalar {SELECT count(*) FROM main.partitions}]>=256} {
                error "record-store-partition-limit"
            }
            ${:db} execute "CREATE TABLE main.$name ${:partitionDDL}"
            ${:db} execute "
                CREATE INDEX main.${name}_time_status ON ${name}(t,status,fid,off,kind);
                CREATE INDEX main.${name}_correlation ON ${name}(identifier,t)
            "
            ${:db} execute {INSERT INTO main.partitions(start,stop,name) VALUES(:start,:stop,:name)}
            :partitionView
        }
        return main.$name
    }

    :method partitionIngest {fid checkpoint tuples batched} {
        set groups {}
        set firsts {}
        set lasts {}
        set floor [:retentionFloor]
        if {![:dict getdef ${:settings} historicalBackfill false]} {
            set floor [expr {max($floor,[:stateGet bootstrapFrom $floor])}]
        }
        set skipped 0
        foreach tuple $tuples {
            if {$batched} {
                set time [lindex $tuple 4]
                if {[lindex $tuple 3]!=0} {
                    error "partitioned ingestion requires valid requests"
                }
            } else {
                if {![dict exists ${:tupleTimes} $tuple]} {
                    error "partitioned SQL ingestion requires tuple routing metadata"
                }
                set time [dict get ${:tupleTimes} $tuple]
            }
            if {![string is double -strict $time] || $time<0 || $time>[clock seconds]+3600} {
                error "record-store-invalid-partition-time"
            }
            if {$time<$floor} {
                incr skipped
                continue
            }
            set bucket [expr {wide(floor($time/${:partitionInterval}))*${:partitionInterval}}]
            dict lappend groups $bucket $tuple
            if {![dict exists $firsts $bucket]} {
                dict set firsts $bucket $time
                dict set lasts $bucket $time
            } else {
                dict set firsts $bucket [expr {min([dict get $firsts $bucket],$time)}]
                dict set lasts $bucket [expr {max([dict get $lasts $bucket],$time)}]
            }
        }
        dict set checkpoint skipped [expr {[:dict getdef $checkpoint skipped 0]+$skipped}]
        if {$groups eq {}} {
            dict set groups empty {}
        }
        set committed {records 0 requestRecords 0 malformedRecords 0 oversizedRecords 0}
        set remaining [dict size $groups]
        try {
            :accessIngestionObserve begin-transaction
            ${:db} transaction {
                dict for {bucket rows} $groups {
                    set target main.row_template
                    if {$bucket ne "empty"} {
                        set target [:partitionEnsure $bucket]
                    }
                    set :recordTable $target
                    set portion $checkpoint
                    incr remaining -1
                    if {$remaining>0} {
                        dict set portion malformed 0
                        dict set portion oversized 0
                        dict set portion skipped 0
                    }
                    set added [:ingestCanonical $fid $portion $rows $batched true]
                    foreach key {records requestRecords malformedRecords oversizedRecords} {
                        dict incr committed $key [dict get $added $key]
                    }
                    if {$bucket ne "empty"} {
                        set n [dict get $added requestRecords]
                        set first [dict get $firsts $bucket]
                        set last [dict get $lasts $bucket]
                        ${:db} execute {
                            UPDATE main.partitions
                               SET first=CASE WHEN n=0 THEN :first ELSE min(first,:first) END,
                                   last=CASE WHEN n=0 THEN :last ELSE max(last,:last) END,n=n+:n
                             WHERE start=:bucket
                        }
                    }
                }
                :sqliteBeforeCommit
            }
        } finally {
            set :recordTable main.rows
            set :tupleTimes {}
        }
        dict set committed insertionBackend [expr {$batched ? "batch-api" : "sql-values"}]
        return $committed
    }

    :method partitionPrune {batchRows} {
        if {![string is integer -strict $batchRows] || $batchRows<1 || $batchRows>10000} {
            error "retention batch rows must be 1..10000"
        }
        if {[${:db} inTransaction]} {
            error "retention requires an idle database session"
        }
        set cutoff [:retentionFloor]
        set peerCutoff [expr {[clock seconds]-[dict get ${:settings} peerRetention]}]
        set now [clock seconds]
        set counts {requests 0 diagnostics 0 minutes 0 peers 0}
        :accessIngestionObserve prepare-retention
        ${:db} execute {CREATE TEMP TABLE nsmcp_retention_candidates(id INTEGER PRIMARY KEY,n INTEGER)}
        try {
            :accessIngestionObserve begin-transaction
            ${:db} transaction {
                :statePut retentionFloor $cutoff
                :accessIngestionObserve partition-selection
                set parts [${:db} execute {SELECT * FROM main.partitions WHERE stop<=:cutoff ORDER BY start LIMIT 1}]
                if {$parts ne {}} {
                    set part [lindex $parts 0]
                    set name [dict get $part name]
                    if {![regexp {^p_[0-9]+$} $name]} {
                        error "record-store-invalid-partition"
                    }
                    :accessIngestionObserve partition-drop
                    ${:db} execute {DROP VIEW main.rows}
                    ${:db} execute "DROP TABLE main.$name"
                    ${:db} execute {DELETE FROM main.partitions WHERE name=:name}
                    :partitionView
                    dict set counts requests [dict get $part n]
                    set first [dict get $part first]
                    set last [dict get $part last]
                    set n [dict get $part n]
                    ${:db} execute {
                        INSERT INTO main.evictions(dropped_at,first_time,last_time,rows,reason)
                        VALUES(:now,:first,:last,:n,'partition');
                        DELETE FROM main.evictions WHERE id NOT IN(
                            SELECT id FROM main.evictions ORDER BY id DESC LIMIT 64)
                    }
                }
                :accessIngestionObserve diagnostic-deletion
                # Loss metadata is small and independent of request partitions.
                ${:db} execute {
                    INSERT INTO temp.nsmcp_retention_candidates
                    SELECT rowid,malformed+oversized+skipped FROM main.scan_counts
                     WHERE completed<:cutoff ORDER BY completed LIMIT :batchRows
                }
                dict set counts diagnostics [${:db} scalar {SELECT coalesce(sum(n),0) FROM temp.nsmcp_retention_candidates}]
                ${:db} execute {DELETE FROM main.scan_counts WHERE rowid IN(SELECT id FROM temp.nsmcp_retention_candidates)}
                :accessIngestionObserve minute-deletion
                ${:db} execute {DELETE FROM main.minutes WHERE rowid IN(
                    SELECT rowid FROM main.minutes WHERE slot<:cutoff ORDER BY slot LIMIT :batchRows)}
                dict set counts minutes [${:db} changes]
                :accessIngestionObserve peer-deletion
                ${:db} execute {DELETE FROM main.peer_addresses WHERE rowid IN(
                    SELECT rowid FROM main.peer_addresses WHERE seen<:peerCutoff ORDER BY seen LIMIT :batchRows)}
                dict set counts peers [${:db} changes]
                :statePut droppedThrough [expr {$cutoff-1}]
                :statePut lastEvictionAt $now
                :accessIngestionObserve commit
            }
            :accessIngestionObserve committed
        } finally {
            ${:db} execute {DROP TABLE temp.nsmcp_retention_candidates}
        }
        return $counts
    }

    :method partitionStatus {} {
        if {${:partitionInterval}==0} {
            return {partitionInterval 0 bootstrapFrom 0}
        }
        set count [${:db} scalar {SELECT count(*) FROM main.partitions}]
        set bootstrap [:stateGet bootstrapFrom 0]
        set control [:stateGet continuous {}]
        return [dict create partitionInterval ${:partitionInterval} partitionCount $count  bootstrapFrom $bootstrap availableFrom [:dict getdef $control availableFrom $bootstrap]  historicalBackfillEnabled [:dict getdef ${:settings} historicalBackfill false]]
    }
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: