Repository navigation
Graphicle 0.1.14
Every crate is at 0.1.14, and graphicle --version prints
graphicle 0.1.14 (core). The Python and TypeScript SDKs are at 0.1.14 too.
After upgrading
- Nothing to do if you do not set
GRAPHICLE_DATABASE_URL: the store
stays one SQLite file,reviews.sqlite3, with the same tables as
0.1.13, and no database server is needed. - To move a data folder to PostgreSQL, set
GRAPHICLE_DATABASE_URLand
rungraphicle --data-dir FOLDER import-sqliteonce. See
docs/configuration.md.
Storage: PostgreSQL as an optional store
With GRAPHICLE_DATABASE_URL set, Graphicle keeps new stores in
PostgreSQL 16 or newer, one schema per data folder, named in the
folder's postgres.json. Andrei Merlescu wrote the PostgreSQL store,
its connection layer, import-sqlite, the backup archive and the load
harness (#19).
- A server on another machine is reached only over TLS, with its
certificate and host name checked;sslmode=disableto such a server
is refused unlessGRAPHICLE_DATABASE_ALLOW_PLAINTEXT=1is set. A
Unix socket or localhost needs no TLS. - The address and password never appear in errors, logs or files
Graphicle writes. Test commands thatfixruns do not inherit the
address, and the other programs Graphicle starts (Git, the graph
engine, the document worker and helpers, the browser opener) inherit
neither it norPGPASSFILE. Graphicle reads a password file (~/.pgpassorPGPASSFILE)
as libpq does, so the password can stay out of the address. - Graphicle needs a role that may create schemas in its database and
nothing more: no superuser. Every connection'ssearch_pathnames
only the data folder's schema, and restore loads only into it. A table
that a backup has and this version does not create is created with
plain column types only (such asbigint,text,double precision,
bytea,boolean,jsonb, or arrays of them); a backup that gives
anything else, such as aCHECKorDEFAULTclause, is refused
before the data folder is made, so a backup file cannot run database
functions while it loads. - Sessions get a statement timeout (600 seconds by default,
GRAPHICLE_DATABASE_STATEMENT_TIMEOUT) and are ended when idle in a
transaction for 10 minutes. graphicle import-sqlitecopies a data folder's SQLite store into
PostgreSQL once, from one snapshot of the file; the SQLite file is
left as it is. It runs only withGRAPHICLE_DATABASE_URLset. It
refuses a SQLite store that has a table or column this version does
not know, or that needs a newer Graphicle. Until the copy has
committed, the folder'spostgres.jsonmarks the new store
unfinished and every command refuses it, so a copy that fails or is
stopped (Ctrl-C, a kill, a reboot) never leaves a store that opens
empty. The refusal says which command left it and how to replace
it:import-sqliteagain for an import (the SQLite file is
untouched), the samerestoreagain for a restore.restoreloads
into a store marked the same way. A second import or restore on the
folder is refused while the first is still copying.- With
GRAPHICLE_DATABASE_URLset, a data folder that has only
reviews.sqlite3is refused by every command, read-only ones too,
until it is imported. - Deletion receipts say
storage.secure_delete: trueon SQLite, which
overwrites deleted rows and then truncates its write-ahead log, and
falseon PostgreSQL, which cannot: a deleted row's bytes stay in the
table's free space until reused, in the server's write-ahead log until
it recycles the segment, and in any WAL archive or base backup.
Graphicle vacuums the store's tables after a deletion; keep the
database on an encrypted volume and expire archives if deleted data
must not be recoverable.
Storage: SQLite under constant reads
- The write-ahead log no longer grows without bound while readers are
always in flight, as long as reads are short, well under the 2
seconds a checkpoint waits.
SQLite's own checkpoint could copy pages back but
never restart the log, so it grew by everything written (8.3 GB on one
service). The store now checkpoints and truncates the log once it
passes 64 MiB, waiting at most 2 seconds for readers; a process runs
one such checkpoint of a store at a time, however many times it opened
the store, and pauses 8 seconds after one that found readers, so
other writers wait at most 2 seconds for it. A checkpoint also waits
for reads that start during its wait, so reads that each take close
to 2 seconds can keep it from finishing, and a long read, such as a
backup or a long request, keeps every checkpoint from cutting the log
back while it runs; the log then grows by what is written meanwhile. This bounds the log between write
transactions, not within one: a single large write, such as storing
the code graph of a large project, still grows the log by what it
writes until it commits. - A request no longer waits for the write lock to be counted against
its workspace's request budget. When the lock is not free within 50
milliseconds, the server checks the budget with a read, serves the
request and holds the count in memory until its next write. A budget
is never exceeded by the requests one server process admits; two
processes serving one data folder at once can exceed it by the counts
the other holds. A killed process loses its held counts. The usage
report lists unwritten counts asunrecorded_requests. - An MCP message no longer takes the write lock to check that its
project exists, and neither do thegraph_as_of, episode and
cross-project edge tools: they read a snapshot. write_lockin a project's status also counts waits of 2 s or more
for the lock in the store's write transactions (slow_waits,
longest_wait_seconds,longest_wait_at). Migrations, request
counts, metering, offboarding and backup loads take the lock on their
own, and their waits are not counted.- Measured with
bench/store-loadon one Databricks serverless machine
(AMD EPYC 9V84), Graphicle undertaskseton 6 CPUs while a second
process indexed 25,543 files, one run per case. With no readers,
indexing finished and the code graph was ready in 134.2 seconds
before the SQLite changes above and in 136.3 seconds with them. With
33 MCP clients each reading as fast as it could, the server answered
25.4 reads a second and 9 of 30,099 reads failed, none of them
"database is locked"; before the changes, the indexer itself failed
with "database is locked" after 297 seconds. The graph is built by
one thread that shares the 6 CPUs with the threads serving reads, so
the graph build alone took 573 seconds under that load, against 108
seconds with no readers and 199 seconds with reads held to 15 a
second. That last case was run with a load harness option that is
not shipped inbench/store-load: a rate cap added for this
measurement. Graphicle has no setting that limits reads; if the graph
must be ready sooner while clients read, have fewer clients reading at
once or give the server more CPUs.
Fixes
- Reads during indexing on SQLite no longer fail with "database is
locked" because their request count waited for the write lock (see
above). - A code graph build that runs while the store is busy keeps its job.
Its lease renewal waited at most a second for the write lock and was
skipped otherwise, so under constant reads the 240 second lease could
run out and a second process built the same graph again from the
start. A renewal now waits up to 5 seconds and retries 5 seconds
later. restoreof a PostgreSQL backup no longer creates a table with the
column types the file gives whatever they are: a type that carried a
CHECK,DEFAULTorGENERATEDclause ran functions of the file's
choosing in the database as the rows loaded. Only plain type names
are accepted, checked before the data folder is made.- An
import-sqliteorrestorethat was stopped during its copy no
longer leaves a data folder whose PostgreSQL store opens empty beside
the SQLite file, and the same command can be run again to replace the
unfinished store, never one whose copy is still running (see above).
An emptypostgres.json, left by a process stopped while creating it,
is refused instead of being filled by whichever process opens it
next; the refusal says to remove only that file. import-sqliteno longer drops, without a word, the data of a column
that this version does not know, and no longer copies a store that
needs a newer Graphicle.- A PostgreSQL store whose creation failed part way, for example in a
migration, no longer leaves its schema behind in the database. - Git, the graph engine, document helpers and the other programs
Graphicle starts no longer inheritGRAPHICLE_DATABASE_URL, which may
hold the database password, orPGPASSFILE. - Read-only commands, such as
graphicle graph, no longer use a data
folder's SQLite store whenGRAPHICLE_DATABASE_URLis set; they
refuse it until it is imported, as the other commands do. - Two stores of one SQLite data folder opened in one process share one
write-ahead log checkpoint and its pause, instead of each running its
own. - The Python SDK's
Client.from_session()finds the session on Windows,
in%LOCALAPPDATA%\Graphicleas the program keeps it, instead of
looking under~/.local/share(Wu Shuwen, #20).
Docs
- docs/configuration.md describes the store choice, the PostgreSQL role,
connections, credentials, locks, backups and deletion, and the bound
on request budgets on SQLite.
Packaging
server.jsonat the repository's root is the MCP Registry entry of
0.1.13, the latest listed release (it named 0.1.12).- New direct dependencies of the program, for the PostgreSQL store:
postgres0.19,r2d20.8,bytes1, and for its TLSrustls0.23
(ring),rustls-native-certs0.8 andtokio-rustls0.26. The licence
pages list them.
Added
bench/store-load/: a load harness that indexes a large repository
into a store while 10 to 33 clients read from it over MCP. It records
read latency and failures, memory, WAL size and fsyncs.
Install
brew install graphicle-ai/tap/graphicle
# or
curl -fsSL https://github.com/Graphicle-AI/graphicle/releases/download/v0.1.14/install.sh | sh -s -- --version 0.1.14On Windows (x86-64, preview), in PowerShell:
$env:GRAPHICLE_VERSION = '0.1.14'; irm https://github.com/Graphicle-AI/graphicle/releases/download/v0.1.14/install.ps1 | iexEach archive's SHA-256 sum is in SHA256SUMS and beside it in a .sha256 file.
graphicle.exe and graphicle-graph.exe in the Windows archive carry a signature from a SignPath test certificate. Windows does not trust that certificate, so it treats them as unsigned programs and may warn before it runs them. Code signing policy