Core
Cross-cutting concerns used by all layers.
Settings
Configuration from environment variables.
from ftm_lakehouse.core.settings import Settings
settings = Settings()
print(settings.uri) # LAKEHOUSE_URI
print(settings.journal_uri) # LAKEHOUSE_JOURNAL_URI
ftm_lakehouse.core.settings.Settings
Bases: BaseSettings
Source code in ftm_lakehouse/core/settings.py
allow_sha1_documents = False
class-attribute
instance-attribute
Allow sha1 checksums during exports (legacy read-only datasets)
duckdb_memory_limit = '8GB'
class-attribute
instance-attribute
DuckDB's memory budget as a byte size (8GB, 512MiB) – it is split
between the workers, so a share of RAM such as 80% is rejected.
duckdb_temp_directory = Field(default_factory=lambda: str(Path(gettempdir()) / 'duckdb'))
class-attribute
instance-attribute
Where DuckDB spills a query that outgrows duckdb_memory_limit – each
DuckDB instance into its own subdirectory, removed on close.
journal_drain_rows = 1000000
class-attribute
instance-attribute
Rows per Arrow table a journal flush hands to the parquet store
journal_pool_size = 5
class-attribute
instance-attribute
Postgres journal connections (adbc)
lock_max_retries = 10
class-attribute
instance-attribute
Retry bound for every wait on a dataset lock: acquiring .LOCK or
.LOCK-MERGE. A lock left behind can be released via ftm-lakehouse
maintenance unlock.
statements_uri = None
class-attribute
instance-attribute
Place parquet different from main uri
workers = Field(default=1, ge=1)
class-attribute
instance-attribute
Processes merge and the export sweep fan their partitions out to
(LAKEHOUSE_WORKERS); 1 runs in-process. duckdb_memory_limit and
the CPU threads are split between them. A sweep uses at most one worker per
(shard, bucket) pair.
write_buffer = '1GiB'
class-attribute
instance-attribute
Arrow bytes a bulk import holds across its partitions before it writes
the largest ones (LAKEHOUSE_WRITE_BUFFER) – a byte size.
zfs_pool = None
class-attribute
instance-attribute
ZFS dataset path the lakehouse's datasets are created under
zfs_statements_pool = None
class-attribute
instance-attribute
ZFS dataset path the statements tables are created under
Path Conventions
Standard paths within the lakehouse.
Path conventions for the FollowTheMoney data lakehouse.
The fundamental idea is to have a convention-based file system layout with well-known paths for metadata and information interchange between processing stages.
All paths are dataset-relative unless otherwise noted.
Dataset Layout
::
lakehouse/
index.json # catalog index
config.yml # catalog configuration
versions/ # versioned snapshots
YYYY/MM/YYYY-MM-DDTHH:MM:SS/
index.json
config.yml
[dataset]/
index.json # dataset index
config.yml # dataset configuration
versions/ # versioned snapshots
YYYY/MM/...
.LOCK # dataset-wide maintenance lock
.LOCK-MERGE # merge / export-sweep lock
.locks/{tenant}/ # operation-specific locks
tags/{tenant}/ # workflow state / cache
archive/ # content-addressed file storage
ab/cd/ef/{checksum}/ # SHA256 split into segments
blob # file blob (stored once)
{origin}.txt # extracted text (one per engine)
statements/ # statement store (shard-partitioned)
shard={shard}/
bucket={bucket}/
origin={origin}/
*.parquet
entities.ftm.json[.zst|gzip] # aggregated entities export
exports/
statistics.json # entity counts, facets
statements.csv[.zst|gzip] # sorted statements
documents.csv[.zst|gzip] # document metadata
documents.{origin}.csv[...] # document metadata (origin-scoped)
graph.cypher # neo4j export (optional)
diffs/ # dirs are codec-free (they double
entities.ftm.json/ # as freshness tags); the files
{ts}.delta.json[.zst|gzip] # entities delta
exports/
documents.csv/
{ts}.diff.csv[.zst|gzip] # documents delta
documents.{origin}.csv/
{ts}.diff.csv[.zst|gzip] # origin-scoped delta
jobs/
runs/
{job_type}/
{timestamp}.json # job run results
CONFIG = StoreKey('config.yml')
module-attribute
user editable config filename
INDEX = StoreKey('index.json')
module-attribute
generated index filename
ARCHIVE = 'archive'
module-attribute
Base path for archive
STATEMENTS = 'statements'
module-attribute
Base path for storing statement data (partitioned by shard, bucket, origin)
EXPORTS = StoreKey('exports')
module-attribute
Base path for exports
Tag Conventions
Standard tags for freshness tracking.
Global tags used to identify actions. Used for cache keys of workflow runs etc.
Export operations don't have constants here – their freshness tag is the
path.* export target itself (e.g. exports/statements.csv), touched
by DatasetJobOperation._run_local after a successful run.
STATEMENTS_UPDATED = 'statements/last_updated'
module-attribute
The statement store's content moved – rows appended (a flush) or an origin dropped. The one clock every export, statistic and diff depends on: a merge rewrites files but changes no content, so it does not touch it.
ARCHIVE_UPDATED = 'archive/last_updated'
module-attribute
Archive last updated (file added)