Skip to main content

linkrs_storage/edge/edge_table/
config.rs

1use linkrs_core::types::Timestamp;
2use linkrs_core::Value;
3
4use crate::edge::RecordFormPreference;
5
6/// Thresholds deciding when a bundled-eligible table is narrow and
7/// read-heavy enough for the inline form.
8///
9/// Single source of truth for the width and access gates: shape admission
10/// still vetoes first, then the observed profile must pass all three gates.
11/// Recommended ranges are documented on each field; values never adapt to
12/// load at runtime.
13#[derive(Debug, Clone, Copy)]
14pub struct RecordFormProfileThresholds {
15    /// Maximum average property-byte width for the inline form.
16    /// Recommended range 8..=16. The bundled slot holds one 8-byte scalar
17    /// word plus a validity bit, so wider averages stay columnar.
18    pub max_inline_avg_bytes: u64,
19    /// Maximum write share `writes / (reads + writes)` for the inline form.
20    /// Recommended range 0.2..=0.4. Above this the table is write-hot and
21    /// stays columnar.
22    pub max_write_share: f64,
23    /// Minimum read-to-write ratio for the inline form. Recommended range
24    /// 2.0..=4.0. Only read-heavy tables migrate inline.
25    pub min_read_to_write_ratio: f64,
26}
27
28impl Default for RecordFormProfileThresholds {
29    fn default() -> Self {
30        Self {
31            max_inline_avg_bytes: 8,
32            max_write_share: 0.3,
33            min_read_to_write_ratio: 3.0,
34        }
35    }
36}
37
38#[derive(Debug, Clone)]
39pub struct EdgeTableConfig {
40    pub initial_vertex_capacity: usize,
41    /// Fixed number of edges allocated per high-degree overflow chunk.
42    pub overflow_chunk_edges: usize,
43    /// Address bits per topology node group: one group covers
44    /// `1 << node_group_bits` bound-endpoint rows. Out groups partition by
45    /// source, in groups by destination.
46    ///
47    /// Locked at table creation: there is no online width-change branch.
48    /// The only adjustment outlet is the offline `EdgeStore::reshard` tool,
49    /// which rebuilds the table group by group and switches the manifest on
50    /// the next checkpoint. Endpoints are expected dense; sparse large
51    /// endpoints must be densified offline via vertex remapping first,
52    /// otherwise only existing groups consume memory and files while the
53    /// span stays wide.
54    pub node_group_bits: u32,
55    /// Write backpressure: max size of the node-group CSR (in bytes)
56    /// before background compaction is requested. Set to 0 to disable.
57    pub max_mutable_csr_bytes: usize,
58    /// Bound for one group append sidecar: disk plus memory op count at or
59    /// above this limit forces a base rewrite on the next checkpoint. Set to
60    /// 0 to disable, leaving sidecar growth unbounded.
61    pub max_append_ops_per_group: usize,
62    /// Region density at or above which a dirty region merges as a whole.
63    /// Below it only rows holding reclaimable entries are visited. Tune
64    /// against checkpoint flushed bytes per live edge; raising it narrows
65    /// merges and never changes the live set.
66    pub region_merge_min_density: f32,
67    /// Group density at or above which a multi-region dirty span merges at
68    /// group scope. Below it merges stay region-scoped. Same tuning source
69    /// and safety as the region threshold.
70    pub group_merge_min_density: f32,
71    /// Automatic maintenance: property compaction on the
72    /// write path when the configured thresholds are exceeded.
73    pub auto_maintenance: AutoMaintenanceConfig,
74    /// User-facing preference for record form selection at table creation.
75    /// `Auto` derives the safe default (pure for empty schemas, otherwise
76    /// columnar); `Columnar` forces the standard multi/single/none strategy
77    /// path; `Bundled` explicitly opts into the single-scalar inline form.
78    pub record_form: RecordFormPreference,
79    /// Opt-in background record-form migration. When enabled, background
80    /// maintenance switches a table to its recommended form once (narrow
81    /// single-scalar columnar tables move inline); the switch fences
82    /// pre-switch WAL redo and requires the next checkpoint, so it stays off
83    /// by default and manual migration remains the primary path.
84    pub auto_migrate_record_form: bool,
85    /// Adapt property column encodings during checkpoints from dirty-column
86    /// values. Enabled by default; disable only to pin encodings manually.
87    pub auto_encode_on_checkpoint: bool,
88    /// Minimum rows for checkpoint-time encoding inference. Smaller columns
89    /// skip inference to avoid fitting elaborate encodings to noise.
90    pub auto_encode_min_rows: usize,
91    /// Declared memory intent for read-heavy paths. `HeapDefault` keeps the
92    /// historical behavior; `ReadSnapshot` marks the read-only snapshot path
93    /// and `BulkLoad` the batch-ingest path so mapping hints follow intent.
94    pub memory_intent: MemoryIntent,
95    /// Width and access gates for automatic bundled migration. Shape
96    /// admission still vetoes first; the observed profile must then pass all
97    /// gates before the background path recommends the inline form.
98    pub record_form_profile: RecordFormProfileThresholds,
99}
100
101/// Declared memory intent for read-heavy paths.
102///
103/// Explicit configuration rather than a best-effort hint: the read-only
104/// snapshot open and the bulk-load path declare which behavior they want, so
105/// huge-page and prefetch choices stay reviewable instead of implicit.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
107pub enum MemoryIntent {
108    /// Default heap behavior: no huge-page hint, mappings stay on base pages.
109    #[default]
110    HeapDefault,
111    /// Read-only snapshot scans: explicitly request transparent huge pages on
112    /// Linux (still best-effort, rejection falls back without failing).
113    ReadSnapshot,
114    /// Batch ingest: skip the huge-page hint to avoid THP pressure during
115    /// sequential bulk writes; mappings stay on base pages.
116    BulkLoad,
117}
118
119/// Thresholds that trigger automatic maintenance on the write path.
120#[derive(Debug, Clone, Copy)]
121pub struct AutoMaintenanceConfig {
122    /// Run property compaction when deleted-but-not-reclaimed property rows
123    /// exceed this ratio of total rows. Recommended range 0.1..=0.25.
124    /// Set to 0.0 to disable.
125    pub property_compact_ratio: f32,
126    /// Minimum tracked tombstones arming the write-path reclaim pass when the
127    /// watermark did not advance. Recommended range 2..=8. Below this count
128    /// with an unchanged watermark the commit skips the group scan entirely.
129    pub reclaim_tombstone_threshold: usize,
130    /// Rebuild the secondary property index when lag since the last rebuild
131    /// reaches this failure count. Set to 0 to disable the count trigger.
132    pub index_rebuild_failure_threshold: u64,
133    /// Rebuild the secondary property index when any lag persists longer
134    /// than this many seconds. Set to 0 to disable the age trigger.
135    pub index_max_stale_secs: u64,
136}
137
138impl Default for AutoMaintenanceConfig {
139    fn default() -> Self {
140        Self {
141            property_compact_ratio: 0.15,
142            reclaim_tombstone_threshold: 4,
143            index_rebuild_failure_threshold: 64,
144            index_max_stale_secs: 300,
145        }
146    }
147}
148
149impl Default for EdgeTableConfig {
150    fn default() -> Self {
151        Self {
152            initial_vertex_capacity: 4096,
153            overflow_chunk_edges: 4096,
154            node_group_bits: crate::edge::node_group::DEFAULT_NODE_GROUP_BITS,
155            max_mutable_csr_bytes: 100 * 1024 * 1024,
156            max_append_ops_per_group: 4096,
157            region_merge_min_density: crate::edge::node_group::REGION_MERGE_MIN_DENSITY,
158            group_merge_min_density: crate::edge::node_group::GROUP_MERGE_MIN_DENSITY,
159            auto_maintenance: AutoMaintenanceConfig::default(),
160            record_form: RecordFormPreference::default(),
161            auto_migrate_record_form: false,
162            auto_encode_on_checkpoint: true,
163            auto_encode_min_rows: 128,
164            memory_intent: MemoryIntent::default(),
165            record_form_profile: RecordFormProfileThresholds::default(),
166        }
167    }
168}
169
170/// Parameters for keyed edge property update
171pub struct UpdateEdgePropertyByKeyParams {
172    pub src: u32,
173    pub dst: u32,
174    pub rank: i64,
175    pub prop_id: u16,
176    pub value: Value,
177    pub ts: Timestamp,
178}