Skip to content

Instantly share code, notes, and snippets.

@arxdsilva
Last active June 15, 2026 19:31
Show Gist options
  • Select an option

  • Save arxdsilva/96c1c2767a4e33449f624da21a5777af to your computer and use it in GitHub Desktop.

Select an option

Save arxdsilva/96c1c2767a4e33449f624da21a5777af to your computer and use it in GitHub Desktop.
block.md

Tenant-Scoped Block Sequence — PostgreSQL Example

A self-contained pattern for giving each tenant its own independent incrementing block_number, without global sequences, using an atomic upsert counter table and a BEFORE INSERT trigger.


Migration: Up

CREATE TABLE tenant_block_sequences (
  tenant_id uuid NOT NULL,
  last_value bigint NOT NULL DEFAULT 0,
  updated_at timestamp NOT NULL DEFAULT current_timestamp,
  CONSTRAINT tenant_block_sequences_pk PRIMARY KEY (tenant_id)
);

CREATE TABLE transaction_blocks (
  id uuid NOT NULL,
  tenant_id uuid NOT NULL,
  block_number bigint,
  block_payload jsonb,
  created_at timestamp NOT NULL DEFAULT current_timestamp,
  last_updated_at timestamp NOT NULL DEFAULT current_timestamp,
  CONSTRAINT transaction_blocks_pk PRIMARY KEY (id),
  CONSTRAINT transaction_blocks_tenant_block_number_unq UNIQUE (tenant_id, block_number)
);

CREATE OR REPLACE FUNCTION next_tenant_block_number(p_tenant_id uuid)
RETURNS bigint AS $$
DECLARE
  next_value bigint;
BEGIN
  IF p_tenant_id IS NULL THEN
    RAISE EXCEPTION 'tenant_id is required to allocate a tenant block number';
  END IF;

  INSERT INTO tenant_block_sequences (tenant_id, last_value)
  VALUES (p_tenant_id, 1)
  ON CONFLICT (tenant_id)
  DO UPDATE SET
    last_value = tenant_block_sequences.last_value + 1,
    updated_at = current_timestamp
  RETURNING last_value INTO next_value;

  RETURN next_value;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION assign_transaction_block_number()
RETURNS trigger AS $$
BEGIN
  IF NEW.tenant_id IS NULL THEN
    RAISE EXCEPTION 'tenant_id is required to allocate a tenant block number';
  END IF;

  IF NEW.block_number IS NULL THEN
    NEW.block_number = next_tenant_block_number(NEW.tenant_id);
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER transaction_blocks_assign_block_number
BEFORE INSERT ON transaction_blocks
FOR EACH ROW
EXECUTE FUNCTION assign_transaction_block_number();

Migration: Down

DROP TRIGGER IF EXISTS transaction_blocks_assign_block_number ON transaction_blocks;
DROP FUNCTION IF EXISTS assign_transaction_block_number();
DROP FUNCTION IF EXISTS next_tenant_block_number(uuid);

DROP TABLE IF EXISTS transaction_blocks;
DROP TABLE IF EXISTS tenant_block_sequences;

How It Works

  • tenant_block_sequences holds one row per tenant. Each insert atomically increments last_value using ON CONFLICT DO UPDATE, guaranteeing no duplicate numbers under concurrent load.
  • next_tenant_block_number(uuid) is the allocation function — safe to call directly if you need to reserve a number before inserting a block.
  • assign_transaction_block_number is a BEFORE INSERT trigger that calls the function automatically, so application code never needs to manage the counter.
  • If you supply an explicit block_number on insert, the trigger leaves it unchanged; the unique constraint still protects against duplicates.

Example Queries

This document shows how to query and write data for:

  • tenant_block_sequences
  • transaction_blocks

The goal is to have an independent incremental block_number per tenant_id.

1) Insert A Transaction Block (Auto Block Number)

block_number is assigned by trigger transaction_blocks_assign_block_number.

INSERT INTO transaction_blocks (
  id,
  tenant_id,
  block_payload
)
VALUES (
  '11111111-1111-1111-1111-111111111111',
  'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
  '{"tx":"payment","amount":1250}'::jsonb
)
RETURNING id, tenant_id, block_number, created_at;

2) Insert Multiple Blocks For The Same Tenant

Each insert increments tenant-local sequence by 1.

INSERT INTO transaction_blocks (id, tenant_id, block_payload)
VALUES
  ('11111111-1111-1111-1111-111111111112', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '{"tx":"invoice"}'::jsonb),
  ('11111111-1111-1111-1111-111111111113', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '{"tx":"refund"}'::jsonb)
RETURNING tenant_id, block_number;

3) Read Blocks For One Tenant (Ordered)

SELECT
  id,
  tenant_id,
  block_number,
  block_payload,
  created_at
FROM transaction_blocks
WHERE tenant_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
ORDER BY block_number ASC;

4) Read Latest Block For One Tenant

SELECT
  id,
  block_number,
  block_payload,
  created_at
FROM transaction_blocks
WHERE tenant_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
ORDER BY block_number DESC
LIMIT 1;

5) Allocate Next Block Number Without Inserting A Block

Useful if the application needs the number first.

SELECT next_tenant_block_number('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') AS reserved_block_number;

6) Insert With Explicit Block Number

If you provide block_number, trigger leaves it unchanged. Unique constraint still applies.

INSERT INTO transaction_blocks (
  id,
  tenant_id,
  block_number,
  block_payload
)
VALUES (
  '11111111-1111-1111-1111-111111111120',
  'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
  9001,
  '{"tx":"manual-replay"}'::jsonb
);

7) Upsert By Tenant + Block Number

INSERT INTO transaction_blocks (
  id,
  tenant_id,
  block_number,
  block_payload,
  last_updated_at
)
VALUES (
  '11111111-1111-1111-1111-111111111130',
  'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
  42,
  '{"tx":"settlement"}'::jsonb,
  current_timestamp
)
ON CONFLICT (tenant_id, block_number)
DO UPDATE SET
  block_payload = EXCLUDED.block_payload,
  last_updated_at = current_timestamp
RETURNING id, tenant_id, block_number, last_updated_at;

8) Inspect Current Sequence State Per Tenant

SELECT
  tenant_id,
  last_value,
  updated_at
FROM tenant_block_sequences
ORDER BY updated_at DESC;

9) Compare Last Sequence Value Vs Last Stored Block

Quick consistency check.

SELECT
  s.tenant_id,
  s.last_value AS sequence_last_value,
  COALESCE(MAX(b.block_number), 0) AS highest_block_number
FROM tenant_block_sequences s
LEFT JOIN transaction_blocks b
  ON b.tenant_id = s.tenant_id
GROUP BY s.tenant_id, s.last_value
ORDER BY s.tenant_id;

10) Transaction Wrapper Example

Typical write in a transaction block.

BEGIN;

INSERT INTO transaction_blocks (
  id,
  tenant_id,
  block_payload
)
VALUES (
  gen_random_uuid(),
  'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
  '{"tx":"batch-posting"}'::jsonb
)
RETURNING id, tenant_id, block_number;

COMMIT;

Notes

  • Sequence is scoped by tenant_id, not global.
  • Allocation is atomic because of INSERT ... ON CONFLICT DO UPDATE.
  • If you use explicit block_number, you are responsible for avoiding conflicts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment