Elasticsearch Source

Nanosync replicates documents out of Elasticsearch using sliced Point-in-Time (PIT) snapshots for the initial backfill and watermark polling for continuous ingestion. Elasticsearch has no native CDC/binlog, so the connector emulates change capture by re-querying a monotonic field (default @timestamp) on a long-lived PIT, with a configurable safety lag to absorb out-of-order writes.

Compatible with Elasticsearch 7.10+, 8.x, and 9.x. OpenSearch is API-compatible but not formally CI-tested in v1.

Prerequisites

Setup

  1. Create a read-only role

    POST /_security/role/nanosync_reader
    {
      "cluster": ["monitor"],
      "indices": [
        {
          "names": ["orders-*", "events-*"],
          "privileges": ["read", "view_index_metadata"]
        }
      ]
    }
  2. Create a user or API key

    User:

    POST /_security/user/nanosync
    {
      "password": "secret",
      "roles": ["nanosync_reader"]
    }

    API key (preferred for service auth):

    POST /_security/api_key
    {
      "name": "nanosync",
      "role_descriptors": {
        "nanosync_reader": {
          "cluster": ["monitor"],
          "indices": [{ "names": ["orders-*"], "privileges": ["read", "view_index_metadata"] }]
        }
      }
    }
  3. Verify each replicated index has _source enabled

    GET /orders-2026.06/_mapping

    If _source.enabled: false is present, the connector will fail preflight. Re-index with _source enabled before adding the index to the pipeline.

  4. Confirm the watermark field

    The default CDC pagination field is @timestamp. If your documents use a different monotonic field (e.g. updated_at, event_time), set cdc_watermark_field in the pipeline properties. The field must be of type date or date_nanos and updated on every write.

    _seq_no is not used as the default watermark. It is per-shard monotonic and gap-tolerant, which complicates multi-shard cursor logic. An opt-in _seq_no mode is planned post-v1.

Connection configuration

connections:
  - name: prod-elasticsearch
    type: elasticsearch
    dsn: "https://es-1.prod:9200"
    properties:
      api_key: "${env:ES_API_KEY}"
      ca_cert: "/etc/ssl/es-ca.pem"

Either dsn (a single URL) or the hosts / cloud_id properties must be provided.

PropertyExampleDescription
hostshttps://es-1:9200,https://es-2:9200Comma-separated cluster URLs. Overrides dsn when set.
cloud_idmy-cluster:dXMtZWFzdC0x...Elastic Cloud identifier. Mutually exclusive with hosts.
usernamenanosyncBasic-auth username.
password${env:ES_PASSWORD}Basic-auth password.
api_key${env:ES_API_KEY}API key. Takes precedence over basic auth when set.
ca_cert/etc/ssl/es-ca.pemPath to a PEM-encoded CA certificate.
tls_skip_verifyfalseDisable TLS certificate verification. Development only.
request_timeout30sPer-HTTP-request timeout.

Pipeline configuration

pipelines:
  - name: orders-pipeline
    source:
      connection: prod-elasticsearch
      properties:
        index_pattern:       "orders-*"
        exclude_indices:     "orders-archive-*"
        snapshot_slices:     "0"           # 0 = auto (primary shard count)
        page_size:           "1000"
        cdc_watermark_field: "@timestamp"
        cdc_poll_interval:   "2s"
        cdc_safety_lag:      "30s"

All pipeline-level properties below — including cdc_watermark_field, include_indices, pit_keep_alive, and request_timeout — are also editable from the visual pipeline canvas. Open the source node and switch to the Advanced tab to configure them without touching YAML.

Source properties

Index selection

PropertyDefaultDescription
index_pattern*Glob over indices to capture (e.g. orders-*). Used only when include_indices is empty.
include_indicesComma-separated list of concrete index names. Overrides index_pattern when set.
exclude_indicesComma-separated list of indices to skip. Applied after pattern/include resolution.

Snapshot

PropertyDefaultDescription
snapshot_slices0 (auto)Number of parallel PIT slices per index. When 0, defaults to the index’s primary shard count, capped by the pipeline’s MaxPartitions hint.
page_size1000Hits per _search page. Keep well below http.max_content_length (default 100 MB).
pit_keep_alive5mPIT keep_alive duration. Refreshed automatically on every page.

Elasticsearch warns that slice_max > number_of_primary_shards actively degrades performance — each slice opens its own shard-level cursor. The default snapshot_slices: 0 resolves to the primary shard count, which is almost always the right answer.

CDC / streaming

PropertyDefaultDescription
cdc_watermark_field@timestampMonotonic field used for CDC pagination. Must be a date or date_nanos field updated on every write.
cdc_poll_interval2sSleep between CDC poll iterations.
cdc_safety_lag30sTrailing-edge offset: only documents with watermark <= now() - safety_lag are read. Absorbs index refresh delay and clock skew.
delete_detectionnoneReserved. Only none is accepted in v1. Setting any other value is a configuration error.

How replication works

  1. Resolve indices. On Connect, Nanosync calls _cat/indices filtered by index_pattern / include_indices, then strips exclude_indices. The list is cached for the connection lifetime and refreshed on ResolveTables.

  2. Fetch mappings. For each index, _mapping is fetched once and converted to an Apache Arrow schema. Object and nested fields are flattened with dotted-path names. Two synthetic columns — _id (primary key) and _index — are always present.

  3. Snapshot — sliced PIT. The planner queries _cat/shards for the primary shard count and emits one partition per slice. Each worker opens a PIT, then loops _search with slice, pit, and search_after until the page is short. The PIT is closed at the end of the partition.

  4. CDC — watermark polling. After the snapshot completes, the connector opens a long-lived PIT and polls every cdc_poll_interval. Each poll reads [last_seen, now() - safety_lag) on the watermark field, sorted ascending, paginating with search_after. The latest watermark seen is persisted to the checkpoint store.

  5. Checkpoint. The CDC watermark is saved to CheckpointStore as a small JSON resume token. PIT IDs are not persisted — they expire server-side, so a fresh PIT is opened on every restart. Snapshot partition completion is tracked in the existing pipeline_run_partitions table.

Schema mapping → Arrow

Leaf primitives map as follows. Object and nested fields are flattened into dotted-path columns.

Elasticsearch typeArrow type
keyword, text, ip, geo_*String
integer, short, byteInt32
long, unsigned_longInt64
float, half_floatFloat32
double, scaled_floatFloat64
booleanBoolean
date, date_nanosTimestamp(µs, UTC)
binaryBinary
object, nestedflatten with dotted-path names
Arrays of primitivesList<T>

Drift

When a CDC or snapshot hit contains a field that is not in the cached schema, the connector emits a SchemaDelta event with the added column(s), updates the cached *arrow.Schema, and continues. This matches the PostgreSQL pgoutput RELATION handling already in nanosync.

Supported operations

OperationSupportedNotes
INSERTYesEmitted as Update (polling cannot distinguish insert from update). Sinks upsert on _id.
UPDATEYesSame as above. PrimaryKeys: ["_id"] is always set.
DELETENoNot captured in v1. Reserved knob: delete_detection: full_diff (future work).
Schema driftYesNew fields surface as SchemaDelta. Type changes pause the pipeline for review.

Monitoring

Key Prometheus metrics:

MetricDescription
nanosync_source_events_total{source_type="elasticsearch"}Events emitted per index and event type
nanosync_source_errors_total{source_type="elasticsearch"}Errors categorised by failure mode
nanosync_source_elasticsearch_watermark_lag_seconds{index}now() - last_seen_watermark. Should hover around cdc_safety_lag in steady state.
nanosync_source_elasticsearch_pit_open{index}1 while a PIT is open for the index, 0 otherwise.

Per-pipeline status:

nanosync metrics pipeline orders-pipeline

Limitations

Troubleshooting

preflight: index "..." has _source disabled Re-index with "_source": { "enabled": true } in the mapping. Without _source, Nanosync cannot reconstruct full documents.

Pipeline stays in snapshot, never advances to CDC The configured cdc_watermark_field is missing on at least one index. Check the mapping for that field and either add it to the source documents or override cdc_watermark_field to a field that exists.

CDC lag grows without bound cdc_poll_interval may be longer than the rate of writes can sustain, or page_size is too small for the document volume. Increase page_size (up to ~5000 for small docs) or decrease cdc_poll_interval. Watch nanosync_source_elasticsearch_watermark_lag_seconds.

Intermittent missed documents cdc_safety_lag is too tight relative to your index refresh_interval or clock skew across nodes. Increase cdc_safety_lag to 60s or more on busy clusters.

elasticsearch: either dsn/hosts or cloud_id must be provided The connection has no endpoint configured. Set dsn on the connection or hosts / cloud_id in the connection properties.