Thursday, July 3, 2025

Search Indexing Demystified: Push vs Pull, and When to Use Each

Search engines are essential to building content-driven user experiences — from marketing websites to product catalogs to knowledge portals. But before you can deliver great search results, you need a solid content indexing strategy.

One of the foundational questions in search implementation is:

“How do we get our content into the search engine index?”

The answer revolves around two key paradigms: Push vs Pull indexing.

In this post, we’ll break down what each means, when to use them, real-world use cases, and how tools like ElasticSearch support both.

1. What is Search Indexing and Why Does It Matter?

Search indexing is the process of collecting, processing, and storing content in a search engine so it can be retrieved when users search.

Indexing ensures:

  • New content is discoverable (e.g., product pages, articles)
  • Updates are reflected in search (e.g., price or availability changes)
  • Deleted content is removed from results
⚠️ Without proper indexing, your search results may be stale, incomplete, or misleading — leading to a poor user experience.

2. Push vs Pull Indexing: Core Concepts





Push Indexing

You actively send content to the search engine via APIs, SDKs, or data pipelines.

When to Use:

  • Real-time updates are essential (e.g., stock, pricing)
  • You own/control the source (e.g., CMS, PIM)
  • Structured content (databases, JSON)

Typical Scenarios:

  • E-commerce platforms updating inventory
  • CMS pushing new articles
  • News feeds or user-generated content systems

Pull Indexing

The search engine retrieves content itself using crawlers, connectors, or scheduled jobs.

When to Use:

  • Indexing public or 3rd-party content
  • Static content where real-time isn’t critical
  • Unstructured sources (HTML, PDFs, docs)

Typical Scenarios:

  • Crawling a blog using sitemap.xml
  • Indexing SharePoint or Google Drive documents
  • Pulling external data via REST APIs

3. Push vs Pull: Decision Matrix


4. ElasticSearch as an Example


Push Indexing in ElasticSearch

  • Use Index API or Bulk API to send data
  • Set up Ingest Pipelines for transformation
    POST /products/_doc/123
{
"name": "Product X",
"description": "High quality...",
"price": 59.99
}

Pull Indexing in ElasticSearch

Via Enterprise Search connectors:

  • Web crawler (starting from sitemap)
  • REST API data source
  • Database connectors (MySQL, MongoDB, etc.)

5. Real-World Use Cases



6. How Other Platforms Handle Indexing



7. Final Thoughts: Designing Your Indexing Pipeline

When deciding between Push and Pull:

Consider:

  • Content structure (structured vs unstructured)
  • Frequency of updates
  • Source system control
  • Access restrictions

Hybrid approaches often work best:

  • Push structured, frequently updated content (e.g., products)
  • Pull public or slowly changing content (e.g., blogs, FAQs)

 Takeaway

Before implementing search, take time to define your indexing strategy — it’s as important as search relevance itself.

If you’re using ElasticSearch:

  • Start with Push for internal systems
  • Explore Pull using crawlers or connectors as your content ecosystem expands

And remember: great search depends not just on what you show, but on how fast and reliably you get it there.

Monday, March 31, 2025

Enabling Custom Validation for Content Fragment Fields in AEM as a Cloud Service – New CF Editor

In my earlier posts, we discussed how to enable Composite MultiField in Content Fragments and how to enable dynamic data fields in the new Content Fragment editor. In this post, we will explore how to enable custom validations for Content Fragment fields. Most of the steps are similar to those outlined in the previous posts. You will create a field in the Content Fragment model, and using the field name, you will register the extension. Please refer to one of the earlier posts for a step-by-step guide to enabling the extension.

While creating a Content Fragment Model, you can set up various out-of-the-box (OOTB) validations for the CF fields, such as MaxLength and Required. These validations should be applied to the overridden fields by fetching the configurations from the model. Additionally, other validations like Email, URL, and Regex can also be applied to the fields from the model.

Note: Please be aware that the content of this blog does not reflect the views of Adobe or my current organization. Before applying this approach, make sure to validate it thoroughly and ensure that it aligns with Adobe's recommendations.



Now, additional validations can be applied through the extension. The out-of-the-box (OOTB) validations, such as Email, URL, and custom regex validations, are applied first, followed by custom validations. For example, if I enable Email validation, the field will only accept valid email addresses. Then, I can add another custom validation rule to reject certain predefined emails, such as [email protected]. This can be achieved through custom regex, but I’m just using this as an example for the demo.

Extension Component to enable additional custom validation Rules:

CustomFieldValidation.js

import React, { useEffect, useState } from "react";
import { attach } from "@adobe/uix-guest";
import { extensionId } from "./Constants";
import { TextField, Provider, View, defaultTheme } from "@adobe/react-spectrum";

const CustomFieldValidation = () => {
  const [connection, setConnection] = useState(null);
  const [model, setModel] = useState(null);
  const [value, setValue] = useState("");
  const [customError, setCustomError] = useState(null);
  const [isInvalid, setIsInvalid] = useState(false);
  const [validationInProgress, setValidationInProgress] = useState(false);

  const validate = (val) => {
    if (!connection?.host?.field) return;

    let error = null;

    // Custom validation rule
    if (typeof val === "string" && val.toLowerCase() === "[email protected]") {
      error = "The value '[email protected]' is not allowed.";
    }

    setCustomError(error);
    setIsInvalid(!!error);

    if (!error || validationInProgress) return;

    setValidationInProgress(true);

    // Delay call to allow host readiness
    setTimeout(() => {
      try {
        connection.host.field
          .setValidationState({ state: "invalid", message: error })
          .catch((err) => {
            console.warn(
              "setValidationState failed:",
              err?.message || JSON.stringify(err)
            );
          })
          .finally(() => setValidationInProgress(false));
      } catch (err) {
        console.warn("setValidationState threw:", err?.message || JSON.stringify(err));
        setValidationInProgress(false);
      }
    }, 1000); // 1s delay for stability
  };

  const handleChange = (val) => {
    setValue(val);

    try {
      connection?.host?.field?.onChange(val).catch((err) =>
        console.warn("onChange failed:", err?.message || JSON.stringify(err))
      );
    } catch (err) {
      console.warn("onChange threw:", err?.message || JSON.stringify(err));
    }

    validate(val);
  };

  useEffect(() => {
    const init = async () => {
      try {
        if (!extensionId) {
          throw new Error("Missing extensionId. Check Constants file.");
        }

        const conn = await attach({ id: extensionId });

        if (!conn?.host?.field) {
          throw new Error("Host field API is unavailable.");
        }

        setConnection(conn);

        const modelData = await conn.host.field.getModel();
        setModel(modelData);

        const defaultValue = (await conn.host.field.getDefaultValue()) || "";
        setValue(defaultValue);
      } catch (err) {
        console.error("Extension init failed:", err?.message || JSON.stringify(err));
      }
    };

    init();
  }, []);

  if (!connection || !model) {
    return (
      <Provider theme={defaultTheme}>
        <View padding="size-200">Loading custom field…</View>
      </Provider>
    );
  }

  return (
    <Provider theme={defaultTheme}>
      <View padding="size-200" width="100%">
        <TextField
          label={model?.fieldLabel || "Custom Field"}
          value={value}
          onChange={handleChange}
          isRequired={model?.required || false}
          placeholder={model?.emptyText || "Enter a value"}
          validationState={isInvalid ? "invalid" : undefined}
          errorMessage={model?.customErrorMsg || customError}
          maxLength={model?.maxLength || undefined}
          width="100%"
        />
      </View>
    </Provider>
  );
};
export default CustomFieldValidation;

Now the custom validation Rules are executed


Sunday, March 9, 2025

Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

While trying to pull a Docker image, the docker pull command was stuck forever without any progress or error.

System Details:

  • Windows 10
  • WSL 2 - Ubuntu
  • Docker Desktop 4.38.0

Issue Faced:


Running docker pull was stuck indefinitely.


Trying to log - docker login -u <username> in using the command prompt failed with this error:
"Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)" 

Even signing into Docker Desktop was not successful.

Login on Docker Hub via browser was working fine, but Docker Desktop was not picking up the login session.

I followed different forums and applied multiple configuration suggestions, including adjusting nameserver settings inside /etc/resolv.conf, but nothing worked.

Also, WSL 2 networking was working fine otherwise — only Docker commands were impacted.

Resolution:

Finally, the issue got resolved after upgrading Docker Desktop to the latest version (4.39.0).
(Older versions may also work — I tried with 4.35.1, and it worked as well).



Thursday, March 6, 2025

Resolving AEM Content Fragment Export to Adobe Target Failure

When exporting AEM Content Fragments as JSON Offers to Adobe Target, you may encounter an error preventing the successful integration. This post details the issue, its root cause, and the steps to resolve it.


Issue

The AEM Content Fragment Export to Adobe Target failed with the following exception:

06.03.2025 12:59:31.223 *DEBUG* [[0:0:0:0:0:0:0:1] [1741287571223] GET /content/dam/content-fragments/test/test-cf/.permissions.json HTTP/1.1]  
com.test.core.filters.LoggingFilter request for /content/dam/content-fragments/test/test-cf/, with selector permissions  

06.03.2025 12:59:40.706 *DEBUG* [[0:0:0:0:0:0:0:1] [1741287580703] POST /content/dam/content-fragments/test/test-cf.cfm.targetexport HTTP/1.1]  
com.test.core.filters.LoggingFilter request for /content/dam/content-fragments/test/test-cf, with selector cfm  

06.03.2025 12:59:40.710 *ERROR* [[0:0:0:0:0:0:0:1] [1741287580703] POST /content/dam/content-fragments/test/test-cf.cfm.targetexport HTTP/1.1]  
com.adobe.cq.dam.cfm.graphql.extensions.querygen.impl.service.QueryGeneratorServiceImpl  
Cannot find Sites GraphQL endpoint resource, cannot generate GraphQL query  

Root Cause

This issue occurs because no GraphQL endpoint is defined for Adobe Target to fetch Content Fragment details. The export process requires a valid GraphQL endpoint to retrieve structured content from AEM and send it to Adobe Target.

Solution

To resolve this issue, follow these steps to define a global GraphQL endpoint in AEM:

  1. Log into AEM and navigate to Tools → General → GraphQL.

  2. Create a new GraphQL endpoint and associate it with /conf/global.

  3. Save and publish the endpoint to make it accessible.

Once configured, the AEM Content Fragment export to Adobe Target will be successful, allowing the fragments to be used in Adobe Target Activities for personalized content experiences.