PHPackages                             niktomo/konayuki - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. niktomo/konayuki

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

niktomo/konayuki
================

High-throughput 63-bit Snowflake ID generator backed by APCu shared memory

v1.0.1(3mo ago)026MITPHPPHP ^8.3CI passing

Since Apr 19Pushed 3mo agoCompare

[ Source](https://github.com/niktomo/konayuki)[ Packagist](https://packagist.org/packages/niktomo/konayuki)[ Docs](https://github.com/niktomo/konayuki)[ RSS](/packages/niktomo-konayuki/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (2)Dependencies (6)Versions (6)Used By (0)

Konayuki
========

[](#konayuki)

[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)[![PHP](https://camo.githubusercontent.com/016f2d707b067b881cb684d2b8623eafdf9a3238addda2d16ff166d19116a7db/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545382e332d373737626234)](composer.json)[![Laravel](https://camo.githubusercontent.com/59b563720c6c273d51150b5c624fb50169d87652956f13ff8726ee93c84b86e5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d313225323025374325323031332d666632643230)](composer.json)

**English** | [日本語](README-ja.md)

> 粉雪 (konayuki) — *powdery snow*. Each flake is tiny, unique, and falls in vast quantities.

High-throughput **63-bit Snowflake ID generator** with a **Hexagonal architecture**: pluggable counter backends (APCu / File / Redis), pluggable `worker_id` strategies (FileLock / Fixed / IP-based / Hostname-hash), and pluggable timestamp strategies — all driven by `.env` and `config/konayuki.php`.

- **2.4M+ IDs / sec** on a single APCu-backed PHP process
- **63-bit fits inside PHP signed `int`** — safe for JSON, MySQL `BIGINT`, etc.
- **k-sortable** time-prefixed IDs (great for indexes)
- **Zero collisions** across 8 forked processes × 10K IDs in CI

> ## ⚠️ Read this before deploying to **multiple hosts**
>
> [](#️-read-this-before-deploying-to-multiple-hosts)
>
> The default `worker_id` strategy is **`file-lock`**, which is safe for a **single host only**.
>
> If you scale out to **2 or more web servers** (5 hosts, 200 hosts, 1000 hosts, ...) and leave the default in place, **every host independently claims `worker_id=0, 1, 2...`**, all hosts produce overlapping ID space, and **you will get duplicate Snowflake IDs across hosts**. This is silent — there is no error message; you simply discover collisions in your database later.
>
> Before deploying to N hosts (N ≥ 2), **pick one of these strategies**:
>
> N hostsRecommendedWhy2–50`hostname-hash` or `fixed`Stable, no collision if hostnames stable50–500`fixed` (orchestrator-injected) or `hostname-hash`Same; `fixed` is deterministic500–1024`fixed` only — `KONAYUKI_MAX_WORKERS=1024` is the hard capHash strategies' collision probability becomes unacceptable&gt; 1024Increase `worker_bits` first (e.g. 12 bits → 4096 workers), then `fixed`10 bits is exhaustedJump to → [worker\_id Strategy](#worker_id-strategy-boot-time-uniqueness) for full details and `.env` snippets.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Quick Start](#quick-start)
- [Configuration Matrix](#configuration-matrix-pick-your-shape)
- [Concurrency Model](#concurrency-model-what-makes-next-multi-process-safe)
- [AtomicCounter (per-ID atomicity)](#atomiccounter-per-id-atomicity)
- [worker\_id Strategy (boot-time uniqueness)](#worker_id-strategy-boot-time-uniqueness)
- [Bit Layout](#bit-layout)
- [Epoch](#epoch)
- [Timestamp Strategy](#timestamp-strategy)
- [Benchmarks](#benchmarks)
- [Why Konayuki?](#why-konayuki)
- [FAQ](#faq)

---

Quick Start
-----------

[](#quick-start)

```
composer require niktomo/konayuki
```

Laravel auto-discovers the service provider. Out of the box, the defaults work for **single-host PHP-FPM / FrankenPHP / Octane**:

```
use Konayuki\Laravel\Facades\Konayuki;

$id = Konayuki::next();        // SnowflakeId
$id->value;                    // 7_123_456_789_012_345  (PHP int, 63-bit)
$id->timestamp();              // 1_712_345_678_000 (ms)
$id->workerId();               // auto-allocated via flock
$id->sequence();               // 0..4095 within ms
```

Default config (no `.env` needed):

PortAdapterBehavior`AtomicCounter`**APCu**shared-memory counter, ~400 ns/ID`WorkerIdAllocator`**FileLock**kernel-released `flock` per process`TimestampStrategy`**Real**wall-clock ms`Clock`**System**`microtime(true)`---

Configuration Matrix (pick your shape)
--------------------------------------

[](#configuration-matrix-pick-your-shape)

**Find your row by host count first**, then by orchestration. Copy the `.env`, done.

### Single host (1 host)

[](#single-host-1-host)

ShapeCounterworker\_id`.env`PHP-FPM / FrankenPHP / OctaneAPCufile-lock*(empty — defaults work)*Multi-process queue workersAPCufile-lock*(empty)*Local testing (no APCu)filefile-lock`KONAYUKI_COUNTER=file`### Multi-host (2–1024 hosts) — `file-lock` is unsafe here

[](#multi-host-21024-hosts--file-lock-is-unsafe-here)

ShapeCounterworker\_id`.env`Kubernetes StatefulSet (stable pod ordinals)APCuhostname-hash`KONAYUKI_WORKER_ID_MODE=hostname-hash`ECS / Nomad / orchestrator gives task indexAPCufixed`KONAYUKI_WORKER_ID_MODE=fixed`
`KONAYUKI_WORKER_ID=${ECS_TASK_INDEX}`Bare metal / VMs, all hosts in one /24 subnetAPCuip-last-octet`KONAYUKI_WORKER_ID_MODE=ip-last-octet`Bare metal / VMs across multiple subnetsAPCuip-hash`KONAYUKI_WORKER_ID_MODE=ip-hash`Containers without APCu, multi-hostredishostname-hash`KONAYUKI_COUNTER=redis`
`KONAYUKI_WORKER_ID_MODE=hostname-hash`### Very large fleets (&gt;1024 hosts)

[](#very-large-fleets-1024-hosts)

10-bit `worker_id` is exhausted at 1024 workers. Either:

1. Increase `worker_bits` to 12 (4096 workers) — costs you 2 bits of sequence; sustained burst capacity drops from 4096 to 1024 IDs / ms / worker
2. Use sharding at the application layer (multiple Konayuki instances with disjoint `worker_id` ranges)

Full env reference is in [`config/konayuki.php`](config/konayuki.php).

---

Concurrency Model (what makes `next()` multi-process safe)
----------------------------------------------------------

[](#concurrency-model-what-makes-next-multi-process-safe)

Konayuki has **only one PHP-userland lock** — the boot-time `flock` for `worker_id`. The per-call sequence increment uses a single atomic CPU instruction in the C extension, with **no PHP-side mutex, no `flock`, no semaphore**. The two mechanisms below are easy to confuse, so the distinction is spelled out explicitly:

① WorkerId flock② Sequence atomic inc**When it runs**Once per process, at boot**Every call to `next()`****What it protects**Each process gets a *unique `worker_id` slot*The per-(worker\_id, ms) sequence stays monotonic across concurrent calls**Implementation**`flock(LOCK_EX | LOCK_NB)` on a lock file (kernel-released on process exit)`apcu_inc()` (atomic at the C-extension level — no PHP-side lock)**Where to find it**`FileLockWorkerIdAllocator::acquire()``ApcuAtomicCounter::nextSequence()`**Cost**~1.5 µs once~400 ns per call### Why both are needed

[](#why-both-are-needed)

```
[Process A boot]
  ├─ FileLock acquires worker_id=0   ← held until process exit
  └─ next() → apcu_inc("seq:0:1234")  ← runs every call
                       ↑
                       (this key belongs only to A — worker_id=0)

[Process B boot]
  ├─ FileLock acquires worker_id=1   ← held until process exit (different slot from A)
  └─ next() → apcu_inc("seq:1:1234")  ← runs every call
                       ↑
                       (this key belongs only to B — worker_id=1)

```

If you removed...What breaks**WorkerId flock only**Two processes both think they're `worker_id=0`, so they share APCu key `seq:0:MS`. Atomic inc still serializes them, but the resulting Snowflake IDs `(worker_id=0, ms, seq)` collide because the IDs are identical to processes on *other* hosts running the same code.**Sequence atomic inc only**Even within one process, two concurrent `next()` calls could read the counter, both see `5`, both write `6` → duplicate sequence within the same ms.> **TL;DR:** flock is for "who am I" (boot-time identity, runs once). `apcu_inc` is for "what's the next number" (runtime atomicity, runs every call). Both are required; neither replaces the other.

`apcu_inc()` is **fully multi-process safe** — implemented as an atomic operation in the APCu C extension (backed by a pthread mutex or spinlock depending on build). The "lock" is **invisible from PHP userland** and adds no syscall on the fast path. From an application code perspective, `AtomicCounter::nextSequence()` looks like an ordinary function call.

---

AtomicCounter (per-ID atomicity)
--------------------------------

[](#atomiccounter-per-id-atomicity)

`AtomicCounter` is the **per-millisecond sequence counter**. The choice determines throughput, durability after process restart, and what infrastructure must be running.

### Comparison

[](#comparison)

AdapterThroughputLatencyLock scopeSurvives restart?Requires**APCu** *(default)***~2.5M IDs/sec**~400 nsone PHP master processno (in-memory)`ext-apcu`**Redis**~12K IDs/sec~86 µscluster-wideyesRedis + `ext-redis`**File**~800 IDs/sec~1.2 mshost-wide (`flock`)yeslocal FS### When to switch

[](#when-to-switch)

 ```
flowchart TD
    A[Need atomic counter] --> B{ext-apcu installed?}
    B -- yes --> C{Single host?}
    B -- no  --> R[Redis]
    C -- yes --> APCU[APCu - default]
    C -- no  --> D{Cross-host atomicity needed?}
    D -- yes --> R
    D -- no  --> APCU2[APCu per host+ unique worker_id]
```

      Loading The **APCu + per-host worker\_id** combination is the production sweet spot: each host runs its own APCu counter (no network hop), and the unique `worker_id` makes cross-host collisions impossible by construction.

### `.env` examples

[](#env-examples)

```
# Default (APCu) — no env needed
# KONAYUKI_COUNTER=apcu

# Switch to Redis
KONAYUKI_COUNTER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# Switch to file-based (testing only — extremely slow)
KONAYUKI_COUNTER=file
KONAYUKI_COUNTER_FILE_DIR=/tmp/konayuki
```

> **Note:** `KONAYUKI_COUNTER` switching is wired in `config/konayuki.php`. If you forked an older copy, ensure the `counter` block is present.

### APCu sizing (`apc.shm_size`)

[](#apcu-sizing-apcshm_size)

Konayuki itself uses ≪ 2 MB. Sizing depends on co-tenants in the same APCu segment:

Co-tenantRecommended `apc.shm_size`Konayuki only**32 MB** (default)+ Laravel `cache.driver=apc`128 MB+ master-data caches (50–500 MB)master size × 2```
; php.ini
apc.shm_size = 128M
apc.enable_cli = 1
```

---

worker\_id Strategy (boot-time uniqueness)
------------------------------------------

[](#worker_id-strategy-boot-time-uniqueness)

`worker_id` is the **unique identifier for the running process / host** that gets baked into every ID. Picking the wrong strategy is the **\#1 cause of duplicate IDs across hosts**.

> ## ⚠️ `file-lock` is single-host only
>
> [](#️-file-lock-is-single-host-only)
>
> `file-lock` (the default) coordinates `worker_id` slots **within one host's filesystem**. Two hosts cannot see each other's lock files, so:
>
> ```
> [Host A] file-lock → worker_id = 0   ┐
> [Host B] file-lock → worker_id = 0   ├── all hosts independently start at 0
> [Host C] file-lock → worker_id = 0   ┘     → ID space fully overlaps
>                                            → Snowflake collisions across hosts
>
> ```
>
>
>
> **Symptom**: no errors, no warnings — duplicate `BIGINT` PKs appear silently in your database, often noticed weeks later when a unique constraint finally trips.
>
> **Rule of thumb**: if you have ≥ 2 web/app servers, **change `KONAYUKI_WORKER_ID_MODE` to one of `fixed`, `hostname-hash`, `ip-hash`, or `ip-last-octet`**.

> **Reminder (see [Concurrency Model](#concurrency-model-what-makes-next-multi-process-safe))**: the strategy below runs **once at boot** to resolve `worker_id` to an `int`. After that, `IdGenerator` just stores the int as a field — there is **no per-`next()` cost** regardless of which strategy you pick. The per-call atomicity is handled separately by `AtomicCounter`.

### Decision flowchart

[](#decision-flowchart)

 ```
flowchart TD
    Start[Need unique worker_id] --> Q1{How many hosts?}
    Q1 -- 1 host --> FL[file-lockdefault, kernel-released flock]
    Q1 -- N hosts --> Q2{Orchestrator injects an ID?e.g. ECS_TASK_INDEX, k8s pod ordinal}
    Q2 -- yes --> FX[fixedKONAYUKI_WORKER_ID=N]
    Q2 -- no --> Q3{Kubernetes StatefulSet?}
    Q3 -- yes --> HH[hostname-hashstable pod-NN names]
    Q3 -- no --> Q4{All hosts in one /24 subnet?}
    Q4 -- yes --> ILO[ip-last-octetdeterministic, human-readable]
    Q4 -- no --> IH[ip-hashprobabilistic, multi-subnet OK]
```

      Loading ### All 5 modes

[](#all-5-modes)

#### 1. `file-lock` *(default — single host)*

[](#1-file-lock-default--single-host)

Each PHP process atomically claims the next free slot via `flock`. The kernel releases the lock on process exit, so crashes / `kill -9` cannot leak slots.

```
# .env (or just leave empty — this is the default)
KONAYUKI_WORKER_ID_MODE=file-lock
KONAYUKI_LOCK_DIR=/var/www/storage/konayuki   # optional, defaults to storage_path('konayuki')
KONAYUKI_MAX_WORKERS=1024                     # must fit worker_bits (default 10 = 1024)
```

**Use when:** one host, multi-process (PHP-FPM workers, queue workers, cron). **Do not use when:** multiple hosts — each host would independently claim worker\_id=0, 1, 2... → guaranteed collision.

> **Performance note:** `acquire()` runs **once per process at boot** (when `IdGenerator` is constructed), not per ID. Measured cost: **~1.5 µs / boot** (see [Benchmarks](#benchmarks)). After boot, `worker_id` is a plain `int` field — zero cost on `next()`.

#### 2. `fixed` *(orchestrator-injected)*

[](#2-fixed-orchestrator-injected)

The caller (Kubernetes / ECS / Nomad / CI) injects a guaranteed-unique ID via env.

```
KONAYUKI_WORKER_ID_MODE=fixed
KONAYUKI_WORKER_ID=7    # whatever your orchestrator provides
```

**Use when:** ECS `ECS_TASK_INDEX`, k8s StatefulSet ordinal (`hostname` ends in `-N`), or any system that gives each instance a numeric index. **Do not use when:** the value is not actually unique (e.g. two pods both reading `WORKER_ID=0` from a shared ConfigMap).

#### 3. `ip-last-octet` *(deterministic, /24 only)*

[](#3-ip-last-octet-deterministic-24-only)

Uses the last octet of the host's primary IPv4 as `worker_id`.

```
KONAYUKI_WORKER_ID_MODE=ip-last-octet
KONAYUKI_MAX_WORKERS=1024     # must be >= 256 (last octet range)
KONAYUKI_IP_OVERRIDE=10.0.0.7 # optional — defaults to auto-detected primary IP
```

**Use when:** all hosts share a single `/24` subnet (or smaller). **DO NOT USE when:** hosts span multiple subnets — `10.0.0.17` and `10.0.1.17` collide on `worker_id=17` → ID collision.

#### 4. `ip-hash` *(probabilistic, multi-subnet)*

[](#4-ip-hash-probabilistic-multi-subnet)

Hashes `crc32(primary_ip) % max_workers`. Works for IPv4 and IPv6.

```
KONAYUKI_WORKER_ID_MODE=ip-hash
KONAYUKI_MAX_WORKERS=1024
KONAYUKI_IP_OVERRIDE=10.0.5.42  # optional
```

**Use when:** multi-host, multi-subnet, no orchestrator injection available. **Collision probability:** roughly N²/(2·max\_workers). At 8 hosts with `max_workers=1024`, ~3% chance of *any* pair colliding. Increase `max_workers` (or upgrade to `fixed`) when at scale.

#### 5. `hostname-hash` *(Kubernetes StatefulSet)*

[](#5-hostname-hash-kubernetes-statefulset)

Hashes `crc32(gethostname()) % max_workers`. Stable when hostnames are stable.

```
KONAYUKI_WORKER_ID_MODE=hostname-hash
KONAYUKI_MAX_WORKERS=1024
KONAYUKI_HOSTNAME_OVERRIDE=app-3   # optional, mainly for testing
```

**Use when:** Kubernetes StatefulSet (pods get stable `name-0`, `name-1` ordinals), on-prem with stable hostnames. **DO NOT USE when:** Docker default randomized hostnames (e.g. `7f3a2b1c4d`) — every restart gives a new `worker_id`, defeating the point.

### Common worker\_id env keys

[](#common-worker_id-env-keys)

EnvDefaultUsed by`KONAYUKI_WORKER_ID_MODE``file-lock`all modes`KONAYUKI_MAX_WORKERS``1024`all modes (must fit `worker_bits`)`KONAYUKI_WORKER_ID`—`fixed` only`KONAYUKI_LOCK_DIR``storage/konayuki``file-lock` only`KONAYUKI_IP_OVERRIDE`auto-detect`ip-last-octet`, `ip-hash``KONAYUKI_HOSTNAME_OVERRIDE``gethostname()``hostname-hash`---

Bit Layout
----------

[](#bit-layout)

```
| 1 sign bit (0) | 41 bits timestamp (ms) | 10 bits worker_id | 12 bits sequence |

```

- **41 bits timestamp** → ~69.7 years from custom epoch
- **10 bits worker\_id** → 1024 unique workers
- **12 bits sequence** → 4096 IDs per ms per worker

Sum **must equal 63** (1 sign bit reserved). Customize via `config/konayuki.php`:

```
'layout' => [
    'timestamp_bits' => 41,
    'worker_bits'    => 10,
    'sequence_bits'  => 12,
],
```

Trade-offs:

If you have...IncreaseDecreaseMany workers (&gt;1024)`worker_bits``sequence_bits`Burst &gt; 4096/ms/worker`sequence_bits``worker_bits`Need &gt;70 yr lifespan`timestamp_bits`one of the others---

Epoch
-----

[](#epoch)

The 41-bit timestamp is **relative to a custom epoch** (not Unix epoch). Default: `2026-01-01 00:00:00 UTC`.

```
KONAYUKI_EPOCH_MS=1767225600000   # 2026-01-01 UTC (default)
```

**Operational rules** (from ADR-0015):

1. Choose epoch ≥ project release date — earlier wastes lifespan.
2. **Never change epoch after deploy** — past IDs would re-decode to wrong timestamps.
3. **Same epoch in all environments** (dev/staging/prod) — required for cross-env comparisons.
4. **Never rewind the system clock** — Snowflake assumes monotonic ms; rewinds can produce duplicate IDs.

---

Sequence Strategy
-----------------

[](#sequence-strategy)

The 12-bit sequence portion of each ID can start at either `0` (default) or a random value per ms window. The choice has zero impact on uniqueness — the `(workerId, ms, sequence)` tuple stays unique either way — and the trade-off only affects **intra-millisecond** ordering.

```
KONAYUKI_SEQUENCE_MODE=monotonic    # production (default)
# KONAYUKI_SEQUENCE_MODE=random     # spread IDs across DB shards / hash buckets
```

ModeBehaviorUse when`monotonic`Each ms window starts sequence at `0`. Smallest IDs, fully k-sortable inside ms.Production. Default.`random`Each ms window starts at `random_int(0, maxSequence)`. Inter-ms order preserved by timestamp prefix; intra-ms order is shuffled.Sparse-traffic dev / staging where you want IDs to spread across DB shards or hash buckets without waiting to fill ms windows naturally.> Why no "jittered timestamp" mode anymore? An earlier prototype perturbed the **timestamp** itself to spread shard distribution. That broke k-sortable ordering for *zero* gain — sequence randomization achieves the same shard-distribution goal while keeping ms-level ordering intact. The timestamp strategy is now `real` only.

---

Benchmarks
----------

[](#benchmarks)

Measured in Docker (PHP 8.4, APCu) on Apple Silicon:

BenchmarkResultThroughput (single process)**2,426,113 IDs/sec**Latency p50416 nsLatency p99542 nsLatency p999625 nsCollision stress (8 procs × 10K)**0 duplicates**Adapter comparison (50K IDs each):

AdapterIDs/secns/IDAPCu2,535,657394Redis11,62186,047File8131,230,011WorkerIdAllocator boot-time cost (`acquire()` runs **once per process** at `IdGenerator` construction — not per ID):

Allocatormeanp50p99`fixed`92 ns83 ns125 ns`ip-hash`136 ns125 ns167 ns`hostname-hash`177 ns167 ns209 ns`ip-last-octet`193 ns167 ns250 ns`file-lock`**1.5 µs**1.5 µs2.0 µs`file-lock` is ~10× slower than the hint-based allocators because it actually opens a file and calls `flock`, but at **1.5 µs / boot** it is invisible compared to typical PHP-FPM / Octane request latency (&gt;1 ms). After boot, all five allocators have the same runtime cost: **zero** (the resolved `worker_id` is just an `int` field on `IdGenerator`).

Run all benchmarks yourself:

```
docker compose run --rm bench
```

---

Why Konayuki?
-------------

[](#why-konayuki)

konayukigodruoyi/php-snowflakegodruoyi/php-id-generatorHexagonal ports✅ 4 ports❌❌APCu shared-memory counter✅ default❌ (filesystem)❌Pluggable worker\_id✅ 5 strategies⚠️ env only⚠️ env only`2.4M+ IDs/sec`✅ measured⚠️ ~50K (filesystem)n/aAPCu wipe detection✅ sentinel-based❌❌Cross-process collision tested✅ in CI❌❌Custom epoch✅✅⚠️ Unix onlyWorker ID layout-aware factories✅ `fromLayout()`❌❌---

FAQ
---

[](#faq)

### What happens if I deploy the default `file-lock` to 200 web servers?

[](#what-happens-if-i-deploy-the-default-file-lock-to-200-web-servers)

You get **silent duplicate IDs across hosts**. Each host independently runs its own FileLockWorkerIdAllocator, sees its own empty lock directory, and claims `worker_id=0` first. Hosts B, C, ... all do the same. Now hosts A, B, C all generate IDs as `worker_id=0`, and the (timestamp, worker\_id, sequence) tuple is no longer globally unique → Snowflake collisions.

There is **no runtime error**. The collision rate depends on your write traffic and the number of hosts that hit the same millisecond. Symptoms surface later as:

- `Integrity constraint violation: 1062 Duplicate entry` on `BIGINT` PK inserts
- Inserts that should have failed silently overwriting due to upserts
- Foreign-key references suddenly pointing to wrong rows

**Fix**: set `KONAYUKI_WORKER_ID_MODE` to `fixed` / `hostname-hash` / `ip-hash` / `ip-last-octet` **before going to production**, and verify with `vendor/bin/konayuki-doctor` on each host.

### Are 63-bit IDs safe to send to clients?

[](#are-63-bit-ids-safe-to-send-to-clients)

Keep `SnowflakeId` as a PHP `int` inside your application. Convert to string only at the **API boundary**.

**JavaScript / TypeScript** — `Number.MAX_SAFE_INTEGER` is `2^53 - 1`; Snowflake IDs reach `2^62`. Always send as a JSON string, parse with `BigInt` on the client:

```
// PHP: serialize as string
'id' => (string) $id->value,
```

```
// TypeScript: parse as BigInt
const id = BigInt(response.id);
```

**C# / Unity** — `long` is 64-bit signed, fully covers the 63-bit range:

```
// PHP: serialize as string
'id' => (string) $id->value,
```

```
// C#: parse to long
long id = long.Parse(response["id"]);
```

**Obfuscation with [Kasumi](https://github.com/niktomo/kasumi)** — if you want to hide sequential nature and internal structure from public API consumers:

```
use Kasumi\Scrambler;

$scrambler = new Scrambler(salt: (int) env('SCRAMBLE_SALT'));

// encode
'id' => (string) $scrambler->scramble($id->value),   // "0yhgff51j5fube" (14-char base36)

// decode (involutory — same call)
$originalValue = $scrambler->scramble($encoded)->toInt();
```

### Why APCu instead of Redis by default?

[](#why-apcu-instead-of-redis-by-default)

- 200× faster (no network hop)
- No additional infrastructure
- Per-host APCu + unique `worker_id` is collision-free by construction

Switch to Redis when you genuinely need cross-host atomicity (rare for ID generation).

### What happens if APCu gets wiped (server restart, OOM)?

[](#what-happens-if-apcu-gets-wiped-server-restart-oom)

Konayuki detects this via a sentinel key (`apcu_add` returns true only on key creation). On detection, it sleeps 1 ms before continuing — guaranteeing the new sequence cannot collide with any pre-wipe ID in the same millisecond.

### Can I use this without Laravel?

[](#can-i-use-this-without-laravel)

Yes. The `Konayuki\Laravel\` namespace is opt-in. Construct `IdGenerator` directly:

```
use Konayuki\IdGenerator;
use Konayuki\Apcu\ApcuAtomicCounter;
use Konayuki\SystemClock;
use Konayuki\Layout;
use Konayuki\RealTimestamp;

$generator = new IdGenerator(
    counter: new ApcuAtomicCounter(),
    clock: new SystemClock(),
    layout: new Layout(epochMs: 1_767_225_600_000),
    timestamp: new RealTimestamp(),
    workerId: 0,
);
```

### How do I diagnose problems?

[](#how-do-i-diagnose-problems)

```
vendor/bin/konayuki-doctor
```

Checks PHP version, APCu, locking type, `apcu_inc` correctness, available memory, `flock` support.

---

Contributing
------------

[](#contributing)

```
docker compose run --rm app    # full QA: pint + phpstan + tests + doctor + bench
docker compose run --rm test   # tests only
docker compose run --rm stan   # PHPStan lv8 only
docker compose run --rm bench  # benchmarks only
```

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance82

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

91d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/1d5f48320c88e00c997df26416eb613aa49554a6d1e719552a4e00fd83bffec5?d=identicon)[tomonori855-hub](/maintainers/tomonori855-hub)

---

Top Contributors

[![niktomo](https://avatars.githubusercontent.com/u/266456726?v=4)](https://github.com/niktomo "niktomo (13 commits)")

---

Tags

laravelapcuidsnowflakedistributed-id

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/niktomo-konayuki/health.svg)

```
[![Health](https://phpackages.com/badges/niktomo-konayuki/health.svg)](https://phpackages.com/packages/niktomo-konayuki)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[renatomarinho/laravel-page-speed

Laravel Page Speed

2.5k1.7M11](/packages/renatomarinho-laravel-page-speed)[vinkius-labs/laravel-page-speed

Laravel Page Speed

2.5k12.5k1](/packages/vinkius-labs-laravel-page-speed)[emargareten/inertia-modal

Inertia Modal is a Laravel package that lets you implement backend-driven modal dialogs for Inertia apps.

90142.9k](/packages/emargareten-inertia-modal)[wearepixel/laravel-cart

A cart implementation for Laravel

1374.8k](/packages/wearepixel-laravel-cart)[hafael/laraflake

Laravel Package based on Twitter Snowflake ID Generator (64 bit ID).

288.1k1](/packages/hafael-laraflake)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
