06_csv_import

command
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 15 Imported by: 0

README

Example 06 — CSV import and export

What it demonstrates

The graph interchange round-trip as a throughput benchmark: generate a seeded edge-list CSV in memory, parse it back into the mutable adjlist builder with csv.ReadIntoCtx, then re-serialise the resulting graph in two formats — CSV via csv.WriteCtx and newline-delimited JSON (JSON Lines / NDJSON) via jsonl.WriteCtx — measuring each leg and confirming the data survives the round trip.

Domain / scenario

A directed follower graph: handles follow other handles. Each node is a 24-char hex id; each edge is one CSV row of src,dst,weight, where the weight (in [1, weight-max]) stands in for interaction strength. The seeded generator gives every node a random out-degree in [follows-min, follows-max] to distinct other nodes — no self-loops, no duplicate (src,dst) pairs — so the graph is simple and the row count is exactly the edge count. That matters for the round-trip invariant: a simple directed graph re-serialises to exactly as many CSV rows as were ingested, with none collapsed by parallel-edge deduplication. Fixing -seed fixes the data shape exactly.

The generated CSV carries a leading # comment row documenting the file; csv.ReadInto skips it, which is why the written CSV is a few bytes smaller than the input (the comment is metadata, not an edge).

How to run

go run ./examples/06_csv_import                          # small deterministic default
go run ./examples/06_csv_import -nodes 1000000 -follows-max 12 -seed 7  # observable-scale run

At the default scale the example does not dump the CSV or JSON Lines to stdout — that would be large and would read as non-deterministic. The round-trip output is written to in-memory buffers; only the deterministic facts, the volatile # telemetry, and a few sample lines of each format are printed.

Scale and flags

Flag Meaning Default Large
-nodes number of follower nodes 1000 1000000
-follows-min minimum out-degree per node 3 3
-follows-max maximum out-degree per node 6 12
-weight-max edge weight drawn from [1, weight-max] 9 9
-sample sample lines of each format to print 3 0
-seed RNG seed (fixes the data shape) 1 any

The default builds a 1000-node graph (~4.5k edges) and round-trips it well under the short-test 60 s budget; the large run is where the serialisers' throughput becomes interesting.

Expected output

The bare lines are deterministic facts (pinned by the regression test). The # -prefixed lines are volatile telemetry — durations, throughput, bytes and heap — that varies per run and per machine.

config.nodes=1000
config.follows=[3,6]
config.weight_max=9
config.seed=1
generated.rows=4475
ingested.rows=4475
graph.nodes=1000
graph.edges=4475
csv.rows_out=4475
jsonl.records_out=5475
jsonl.expected_records=5475
roundtrip.csv_reread_rows=4475
roundtrip.edges=4475
# parse.row_rate=1650074 rows/s       # telemetry — varies, never pinned
# csv.serialise.throughput=254.76 MiB/s
# jsonl.serialise.throughput=128.94 MiB/s
# bytes.in_csv=227.30 KiB
# bytes.out_csv=227.25 KiB
# bytes.out_jsonl=453.30 KiB
# mem.heap_alloc=981.80 KiB
# sample.csv (first 3 rows):
# 5b18db94b4d338a5143e6340,f1a6fbcd8704e119196fcc28,6
# sample.jsonl (first 3 records):
# {"type":"node","id":"5b18db94b4d338a5143e6340"}

Note jsonl.records_out (5475) = graph.nodes (1000) + graph.edges (4475): JSON Lines emits one record per node, then one per edge.

Evidence it collects

For an interchange subject the example reports (as # telemetry):

  • Parse throughputparse.row_rate (rows/s) and parse.throughput (MiB/s) for csv.ReadIntoCtx.
  • Serialise throughputcsv.serialise.* and jsonl.serialise.* (rows or records/s, and MiB/s) for csv.WriteCtx / jsonl.WriteCtx.
  • Bytes in/out per formatbytes.in_csv, bytes.out_csv, bytes.out_jsonl, plus bytes.per_edge_csv and bytes.per_record_jsonl so the two encodings' density is comparable.
  • Live heapmem.heap_alloc after a forced GC, with the growth, total-alloc and GC count of the whole pipeline.

The deterministic results (as bare facts) are the ingested row count, the written row/record counts, and the round-trip edge count. Scale the run up with -nodes and -follows-max and watch the per-format throughput and the JSON-Lines-vs-CSV byte ratio: JSON Lines is roughly twice the size because every field name is repeated on every record.

Key APIs

  • graph/io/csv.ReadIntoCtx — parse an edge-list CSV (skipping # comments) into an adjlist.AdjList[string, int64], honouring context cancellation; returns the row count.
  • graph/io/csv.WriteCtx — serialise the adjacency list back to CSV, one edge per row; returns the row count.
  • graph/io/csv.DefaultOptions — the default CSV layout (, delimiter, no header, # comment character, directed simple graph).
  • graph/io/jsonl.WriteCtx — serialise the same graph as JSON Lines: a node record per node followed by an edge record per edge; returns the record count.
  • graph/adjlist.AdjList.Order / .Size — the node and edge counts of the parsed graph.

Further reading

Documentation

Overview

Example 06_csv_import — an interchange round-trip benchmark for the edge-list serialisers: generate a seeded follower graph as CSV in memory, parse it back with csv.ReadIntoCtx, then re-serialise the resulting graph as CSV with csv.WriteCtx and as newline-delimited JSON (JSON Lines) with jsonl.WriteCtx, measuring each leg.

It exists to exercise the io/csv and io/jsonl packages on a realistic, scale-parametrised dataset and to report the evidence that matters for an interchange subject — parse and serialise throughput (rows/s, MiB/s), bytes in and out for each format, and live heap — while pinning a deterministic data shape with a regression test.

Model

The dataset is a directed follower graph: handles follow other handles. Each node is a 24-char hex id; each edge is one CSV row of

src,dst,weight

where weight is a small integer in [1, weightMax] (think interaction strength). Every user is given a random out-degree in [followsMin, followsMax] to distinct other users — no self-loops and no duplicate (src,dst) pairs — so the graph is simple and the row count is exactly the edge count. This matters for the round-trip invariant: a simple directed graph re-serialises to exactly as many CSV rows as were ingested, with none collapsed by parallel-edge deduplication.

Pipeline

  1. Generate a CSV edge list from the seeded RNG into an in-memory buffer (this is the "bytes in").
  2. Parse it back with csv.ReadIntoCtx, genuinely exercising the CSV reader (this is the parse leg).
  3. Serialise the parsed graph to an in-memory buffer with csv.WriteCtx (the CSV serialise leg; "bytes out, csv").
  4. Serialise the same graph with jsonl.WriteCtx (the JSON Lines serialise leg; "bytes out, jsonl"). JSON Lines emits one record per node followed by one record per edge, so its line count is nodes + edges.
  5. Re-parse the written CSV with csv.ReadIntoCtx and confirm the edge count is unchanged — the round-trip invariant.

At the default scale the example does NOT dump the CSV or JSON Lines to stdout — that would be large and would read as non-deterministic. The round-trip output is written to in-memory buffers; only the deterministic facts and the volatile "# " telemetry are printed, plus a few sample lines of each format so a reader can see the shape.

Scale

Run with no flags, the example builds a small deterministic default (1000 users, 3-6 follows each) that stays well under the short-test budget. Every dimension is a flag, so the same binary scales up to a size where the serialisers' throughput is actually observable:

go run ./examples/06_csv_import -nodes 1000000 -follows-max 12 -seed 7

The deterministic data shape is reproducible for a fixed -seed; only the telemetry (lines prefixed with "# ") varies between runs and machines.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL