GIP blackout window (sequential)
Configuration, date math, Mongo state, and ingress/egress flow for GIP sequential blackout when document business day is past the stored sync line
What this is
In GIP, the term blackout window (sometimes “black window” in code or logs) is the per-store, per-connector–event record that says: “synchronous processing is only allowed for documents on or before this calendar line until earlier work catches up.”
When a connector–event mapping is marked sequential (isSequential: true), ingress may block new jobs if the business transaction date (by calendar day) is strictly after the stored blackout_date. Egress, after a terminal job result, updates that window (on failure) or clears it (on success) and can schedule deferred re-executions via a Mongo-backed retry queue and a small scheduler in the egress app.
Runtime code is split between apps/ingress-service, apps/egress-service, configuration via apps/app-connector-service (ACS), and shared Mongo + utilities in @libs/dip-v2.
When to use it
Enable isSequential on a connector–event mapping when:
- Events should be applied in business date order per store (or another key you read into
storeIdviasequentialKey), and - A failed or stuck day should block or sequence later business days until the work succeeds or the window is cleared by egress.
If isSequential is false, the blackout and sequential-checkpoint paths described on this page do not run for that mapping.
Configuration you must set
Connector–event mapping (DipV2 + ACS)
| Field | When required | Role |
|---|---|---|
isSequential | Set true to use GIP blackout and sequential logic | Master switch. |
sequentialKey | Required in ACS when isSequential (create) | Dot path in the inbound JSON to the value read as storeId (e.g. store/branch), via get(dto, sequentialKey) in ingress. |
transactionDatePath | Required in ACS when isSequential (create) | Dot path to the business transaction date for calendar-day checks and job.transactionDate. If empty or only whitespace, ingress uses now and logs a warning. If the path is set but the value is missing or not a valid date, the job is BLOCKED (validation error) before blackout comparison. |
ACS validation (CreateConnectorEventDto / ConnectorEventUpdateDto in apps/app-connector-service/src/dto/connector-event.ts): when isSequential is true, sequentialKey and transactionDatePath are required (@ValidateIf on those fields).
Outbound event (refDocPath)
The outbound event’s refDocPath (see Data model reference) drives refDocNo on the event log and job. That value participates in retry queue identity together with ssoEnterpriseId and connectorAppEventId (see below).
Egress environment
| Variable | Effect |
|---|---|
GIP_BLACKOUT_MAX_RETRIES | Max attempts for the GIP blackout retry path; default 10 if unset. When the next attempt would exceed this, the retry queue row is deleted and a warning is logged. |
IS_QUEUE_WORKER | Must be true for the GIP blackout retry scheduler to run (see Egress scheduler). |
Date and identity helpers (@libs/dip-v2)
libs/dip-v2/src/utils/gip-blackout.ts defines the core comparisons:
computeGipBlackoutDate(transactionDate)— Takes the calendar day of the transaction, adds one day, and returnsYYYY-MM-DD(string). This is the synthetic “next line” used when persistingblackout_dateon the window document and when updating / removing rows inGipBlackoutWindowRepository.isDocDateAfterBlackoutDate(transactionDate, blackoutDate)—truewhen the document’s calendar day is strictly after the blackout row’s calendar day (ignores time-of-day within the same calendar date).gipBlackoutEventName(ssoEnterpriseId, connectorAppEventId)—"{enterpriseId}-{mappingObjectId}"for retry queueevent_name.gipRetryConsumerKey(ssoEnterpriseId, connectorAppEventId)—"{enterpriseId}_{mappingObjectId}"forconsumer_name.
// libs/dip-v2/src/utils/gip-blackout.ts (conceptual)
export function computeGipBlackoutDate(transactionDate: Date): string {
return moment(moment(transactionDate).format('YYYY-MM-DD'), 'YYYY-MM-DD')
.add(1, 'days')
.format('YYYY-MM-DD');
}
export function isDocDateAfterBlackoutDate(
transactionDate: Date,
blackoutDate: Date | string,
): boolean {
const docDay = moment(transactionDate).format('YYYY-MM-DD');
const blackoutDay = moment(blackoutDate).format('YYYY-MM-DD');
return moment(docDay, 'YYYY-MM-DD').isAfter(moment(blackoutDay, 'YYYY-MM-DD'));
}Mongo collections (DipV2 connection)
gip_outbound_sync_blackout_windows — blackout window
Model: GipBlackoutWindowModel. Unique index on (ssoEnterpriseId, connectorAppEventId, storeId).
| Field | Meaning |
|---|---|
ssoEnterpriseId | Tenant. |
connectorAppEventId | Connector–event mapping id. |
storeId | Store id string from sequentialKey (as resolved at ingress). |
blackout_date | Date derived from computeGipBlackoutDate(job.transactionDate) when saving/updating. |
doc_no / error_doc_no | Reference document numbers associated with the window (see GipBlackoutWindowRepository.saveBlackWindow / removeDocno). |
isNotificationSend | Flag toggled when egress calls updateNotificationStatus after a failed terminal run that creates/updates the window. |
gip_outbound_retry_queues (GIP outbound retry queue)
Model: GipBlackoutRetryQueueModel. Indexed on next_retry_at for sweeps. Important fields: event_name, doc_no, req (JSON with jobId for re-execution), event_rety_count, next_retry_at, consumer_name, blackout_window_details (e.g. blackout_source vs blackout_dependent).
gip_history_sync_blackout_window — history
When a window row is deleted after a successful removeDocno clears the last error docs, a snapshot is written to history before deletion (see GipBlackoutWindowRepository.removeDocno).
Ingress: job creation and first blackout check
File: apps/ingress-service/src/tasks/request-handler.ts.
- For each connector–event mapping with
isSequentialtrue, readstoreIdfrom the payload withget(dto, connectorEventMapping.sequentialKey). - Resolve
transactionDatefromtransactionDatePath(or now with a warning if the path is empty). If the path is set but missing/invalid, the job is BLOCKED with an explicit error (no blackout logic). findBlackWindowfor{ ssoEnterpriseId, connectorAppEventId, storeId }.- If a window exists and
isDocDateAfterBlackoutDate(transactionDate, blackWindow.blackout_date)is true:- Call
saveBlackWindowwith the same store/mapping/enterprise,refDocNo, andtransactionDate. - Set job
BLOCKEDanderror: 'GIP blackout dependent: waiting for prior sync window'.
- Call
- Sequential checkpoint (same file): if not blocked by blackout, job sync checkpoint may still BLOCK the job for other sequential rules (
'Job is blocked due to sequential issue') — that is ordering, not the same as blackout. See Egress runtime and retry for the overall job pipeline. - After jobs are created, for each job that is BLOCKED with the GIP blackout message, enqueue a row via
gipBlackoutRetryQueueRepo.saveRetryQue, passing the current blackout document from anotherfindBlackWindow(used to populateblackout_window_detailsin the queue repo layer).
Egress: after terminal job status
File: apps/egress-service/src/tasks/task-executor.ts — applyGipBlackoutAfterTerminalStatus.
Runs only if the mapping is isSequential and job.storeId is set.
| Terminal status | Action |
|---|---|
| SUCCESS | removeDocno on the blackout window (and related cleanup), then deleteRetryQue for this job’s GIP filter (consumer_name + doc_no + event_name). |
| FAILED or filter-validation-failed | saveBlackWindow to upsert/advance the window, optionally updateNotificationStatus, then upsert the retry queue with backoff and max retry cap. |
Backoff (minutes) before the next next_retry_at: 4^(prev+1) + (10 * prev + 1) where prev is the prior event_rety_count (or 0). That interval is added to Date.now() in milliseconds.
// apps/egress-service/src/tasks/task-executor.ts (excerpt)
const maxRetries = Number(
this.configService.get<string>('GIP_BLACKOUT_MAX_RETRIES') ?? '10',
);
const totalMin = Math.pow(4, prev + 1) + (10 * prev + 1);
const nextRetryAt = Date.now() + totalMin * 60 * 1000;Egress: GipBlackoutRetryScheduler
File: apps/egress-service/src/scheduler/gip-blackout-retry.scheduler.ts.
- Fires on a fixed 5 minute interval (
5 * 60 * 1000ms). - If
IS_QUEUE_WORKER !== 'true', the handler returns immediately (no sweep). - Loads retry rows with
next_retry_at≤ now, parsesreqJSON forjobId, and callsTaskExecutorService.executeJob(new ObjectId(jobId)).
End-to-end flow (mermaid)
flowchart TD
Payload[Inbound JSON payload] --> RH[RequestHandler]
RH --> Seq{isSequential?}
Seq -->|no| JobPlain[Create job - no blackout path]
Seq -->|yes| Store[Read storeId from sequentialKey]
Store --> Tx[Resolve transactionDate from transactionDatePath]
Tx --> FindBw[findBlackWindow]
FindBw --> After{isDocDateAfterBlackoutDate?}
After -->|yes| BlockJob[Job BLOCKED - GIP blackout dependent]
After -->|no| Checkpoint[Job sync checkpoint PENDING or BLOCK other]
BlockJob --> SaveQue[saveRetryQue with blackout ref]
Checkpoint --> Dispatch{PENDING and async?}
Dispatch -->|yes| Queue[Kafka dispatches job]
Queue --> Eg[TaskExecutorService.executeJob]
Bridge[Other job paths] --> Eg
Eg --> Terminal[Terminal status]
Terminal --> Bw[applyGipBlackoutAfterTerminalStatus]
Bw -->|SUCCESS| Remove[removeDocno + deleteRetryQue]
Bw -->|FAIL or filter failed| SaveBw[saveBlackWindow + saveRetryQue backoff]
SaveBw --> Sch[GipBlackoutRetryScheduler 5m]
Sch --> EgOperational notes
- Symptom: job error or log text starting with
GIP blackout dependent— a document’s calendar day is after the current blackout_date for that store + mapping. - Tuning:
GIP_BLACKOUT_MAX_RETRIES; the sweep interval is 5 minutes in code unless changed in a future release. - Where to configure mapping fields: operators use the G1 DIP / ACS surface backed by
app-connector-serviceDTOs, not the ingress/egress apps directly.
Related
- Data model reference —
transactionDatePathand mapping fields - Ingress — async and sync —
RequestHandler - Egress runtime and retry —
TaskExecutorServiceand Kafka - ACS API overview — REST surface for connector–event mappings
Seeding public apps and events
JSON seeders under storage/seeders, POST /acs/common/seed, and POST /acs/common/sync-public-events
GIP Debezium — MySQL and Mongo bootstrap
How zwing-connect apps/debezium-connector provisions Kafka Connect for GIP MySQL (Debezium) and GIP Mongo (Mongo source), with shared config in libs/dip-v2