Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataStore Triggers Full Sync on Every App Start, Even Without Changes #14218

Closed
3 tasks done
ertan95 opened this issue Feb 16, 2025 · 2 comments
Closed
3 tasks done

DataStore Triggers Full Sync on Every App Start, Even Without Changes #14218

ertan95 opened this issue Feb 16, 2025 · 2 comments
Labels
DataStore Related to DataStore category

Comments

@ertan95
Copy link

ertan95 commented Feb 16, 2025

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

DataStore

Amplify Version

v6

Amplify Categories

auth

Backend

Amplify CLI

Environment information

# Put output below this line
System:
    OS: macOS 14.5
    CPU: (12) arm64 Apple M2 Pro
    Memory: 68.45 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.20.3 - ~/.nvm/versions/node/v18.20.3/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v18.20.3/bin/yarn
    npm: 10.8.3 - ~/.nvm/versions/node/v18.20.3/bin/npm
    pnpm: 9.15.0 - ~/.nvm/versions/node/v18.20.3/bin/pnpm
    Watchman: 2024.12.02.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 133.0.6943.98
    Safari: 17.5
  npmPackages:
    @aws-amplify/core: ^6.0.21 => 6.0.21 
    @aws-amplify/core/internals/adapter-core:  undefined ()
    @aws-amplify/core/internals/aws-client-utils:  undefined ()
    @aws-amplify/core/internals/aws-client-utils/composers:  undefined ()
    @aws-amplify/core/internals/aws-clients/cognitoIdentity:  undefined ()
    @aws-amplify/core/internals/aws-clients/pinpoint:  undefined ()
    @aws-amplify/core/internals/providers/pinpoint:  undefined ()
    @aws-amplify/core/internals/utils:  undefined ()
    @aws-amplify/core/server:  undefined ()
    @aws-amplify/react-native: ^1.0.22 => 1.0.22 
    @aws-amplify/storage: ^6.0.21 => 6.0.21 
    @aws-amplify/storage/s3:  undefined ()
    @aws-amplify/storage/s3/server:  undefined ()
    @aws-amplify/storage/server:  undefined ()
    @babel/core: ^7.23.7 => 7.23.7 
    @ertan95/react-native-document-scanner: ^2.1.6 => 2.1.6 
    @ertan95/react-native-perspective-image-cropper: ^0.6.2 => 0.6.2 
    @expo/config-plugins: ~7.2.2 => 7.2.5 
    @expo/metro-config: ~0.10.0 => 0.10.7 
    @expo/prebuild-config: ~6.2.4 => 6.2.6 
    @expo/vector-icons: ^13.0.0 => 13.0.0 
    @react-native-async-storage/async-storage: 1.18.2 => 1.18.2 
    @react-native-community/netinfo: 9.3.10 => 9.3.10 
    @react-navigation/bottom-tabs: ^6.5.12 => 6.5.12 
    @react-navigation/native: ^6.1.10 => 6.1.10 
    @react-navigation/stack: ^6.3.21 => 6.3.21 
    @reduxjs/toolkit: ^2.2.7 => 2.2.7 
    @reduxjs/toolkit-query:  1.0.0 
    @reduxjs/toolkit-query-react:  1.0.0 
    @reduxjs/toolkit-react:  1.0.0 
    @shopify/flash-list: 1.4.3 => 1.4.3 
    @tamagui/babel-plugin: 1.82.8 => 1.82.8 
    @tamagui/font-inter: 1.82.8 => 1.82.8 
    @tamagui/react-native-media-driver: 1.82.8 => 1.82.8 
    @tamagui/shorthands: 1.82.8 => 1.82.8 
    @tamagui/themes: 1.82.8 => 1.82.8 
    @tensorflow/tfjs: ^4.15.0 => 4.15.0 
    @tensorflow/tfjs-core: ^4.15.0 => 4.15.0 
    @tensorflow/tfjs-react-native: ^1.0.0 => 1.0.0 
    @types/lodash: ^4.14.202 => 4.14.202 
    @types/react: ~18.2.47 => 18.2.73 
    @typescript-eslint/eslint-plugin: ^6.18.0 => 6.18.0 
    @typescript-eslint/parser: ^6.18.0 => 6.18.0 
    @welldone-software/why-did-you-render: ^8.0.3 => 8.0.3 
    HelloWorld:  0.0.1 
    aws-amplify: ^6.0.21 => 6.0.21 
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/data:  undefined ()
    aws-amplify/data/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    babel-plugin-module-resolver: ^5.0.0 => 5.0.0 
    babel-plugin-transform-remove-console: ^6.9.4 => 6.9.4 
    eslint: ^8.56.0 => 8.56.0 
    eslint-config-prettier: ^9.1.0 => 9.1.0 (8.10.0)
    eslint-config-universe: ^12.0.0 => 12.0.0 
    eslint-plugin-hooks: ^0.4.3 => 0.4.3 
    eslint-plugin-import: ^2.29.1 => 2.29.1 
    eslint-plugin-jest: ^27.6.1 => 27.6.1 
    eslint-plugin-prettier: ^5.1.2 => 5.1.2 
    eslint-plugin-react: ^7.33.2 => 7.33.2 
    eslint-plugin-simple-import-sort: ^10.0.0 => 10.0.0 
    eslint-plugin-unused-imports: ^3.0.0 => 3.0.0 
    example:  1.0.0 
    expo: 49.0.23 => 49.0.23 (49.0.0)
    expo-cached-image: ^49.0.2 => 49.0.2 
    expo-file-system: ~15.4.5 => 15.4.5 
    expo-font: ~11.4.0 => 11.4.0 
    expo-haptics: ~12.4.0 => 12.4.0 
    expo-image-manipulator: ~11.3.0 => 11.3.0 
    expo-linking: ~5.0.2 => 5.0.2 
    expo-random: ^14.0.1 => 14.0.1 
    expo-router: ^2.0.14 => 2.0.15 
    expo-splash-screen: ~0.20.5 => 0.20.5 
    expo-status-bar: ~1.6.0 => 1.6.0 
    expo-system-ui: ~2.4.0 => 2.4.0 
    expo-web-browser: ~12.3.2 => 12.3.2 
    fs-extra: ^11.2.0 => 11.2.0 (8.1.0, 9.0.0, 9.1.0)
    i18next: ^23.11.1 => 23.11.1 
    jest: ^29.7.0 => 29.7.0 
    jszip: ^3.10.1 => 3.10.1 
    metro-minify-terser: ^0.80.9 => 0.80.9 (0.76.0)
    plist: ^3.1.0 => 3.1.0 
    prettier: ^3.1.1 => 3.1.1 
    react: 18.2.0 => 18.2.0 
    react-content-loader: ^7.0.0 => 7.0.0 
    react-content-loader/native:  undefined ()
    react-dom: ^18.2.0 => 18.2.0 
    react-i18next: ^14.1.0 => 14.1.0 
    react-native: 0.72.10 => 0.72.10 
    react-native-admob-native-ads: ^0.7.6 => 0.7.6 
    react-native-animatable: ^1.4.0 => 1.4.0 
    react-native-animated-dots-carousel: ^1.0.2 => 1.0.2 
    react-native-chart-kit: ^6.12.0 => 6.12.0 
    react-native-circular-progress: ^1.4.0 => 1.4.0 
    react-native-country-flag: ^2.0.2 => 2.0.2 
    react-native-file-logger: ^0.5.5 => 0.5.5 
    react-native-fs: ^2.20.0 => 2.20.0 
    react-native-gesture-handler: ~2.12.0 => 2.12.1 
    react-native-google-mobile-ads: ^14.5.0 => 14.5.0 
    react-native-localize: ^3.1.0 => 3.1.0 
    react-native-mlkit-ocr: ^0.3.0 => 0.3.0 
    react-native-otp-entry: ^1.8.2 => 1.8.2 
    react-native-permissions: ^4.1.5 => 4.1.5 
    react-native-purchases: ^7.26.2 => 7.26.2 
    react-native-purchases-ui: ^7.26.2 => 7.26.2 
    react-native-rate: ^1.2.12 => 1.2.12 
    react-native-reanimated: ~3.3.0 => 3.3.0 
    react-native-render-html: ^6.3.4 => 6.3.4 
    react-native-safe-area-context: ^4.9.0 => 4.9.0 
    react-native-screens: ~3.22.0 => 3.22.1 
    react-native-svg: ^13.9.0 => 13.9.0 
    react-native-toast-notifications: ^3.4.0 => 3.4.0 
    react-native-walkthrough-tooltip: ^1.6.0 => 1.6.0 
    react-native-web: ~0.19.10 => 0.19.10 
    react-navigation-shared-element: ^3.1.3 => 3.1.3 
    react-redux: ^9.1.2 => 9.1.2 
    redux: ^5.0.1 => 5.0.1 
    tamagui: 1.82.8 => 1.82.8 
    typescript: ^5.3.3 => 5.3.3 
  npmGlobalPackages:
    corepack: 0.28.0
    expo-cli: 6.3.12
    npm: 10.8.3
    package-json-validator: 0.6.4
    pnpm: 9.15.0
    vercel: 37.1.1
    why-is-node-running: 2.3.0
    yarn: 1.22.22

Describe the bug

Every time the app starts, DataStore performs a full sync, even though no changes have been made to the local or remote data. The expectation is that after the initial sync, subsequent app starts should not fetch the same updates again if no modifications have been made. I'm not sure if I'm missing some configs. I setup everything according to the docs. Hope someone can help.

Expected behavior

I receive these kind of logs on every app start
LOG modelSynced: {"isFullSync":true,"isDeltaSync":false,"counts":{"new":0,"updated":21,"deleted":0}}

After the second start I would expect the counts to be zero

Reproduction steps

Initialize Amplify and DataStore with the following configuration:

DataStore.configure({
  errorHandler: (error) => console.error('DataStore Error:', error),
  conflictHandler: ({ modelConstructor, remoteModel, localModel }) => DISCARD,
  syncExpressions: [
    syncExpression(Card, async () => {
      const deviceId = await StorageHelper.getDeviceId();
      return (card) => card.lastUpdatedBy.ne(deviceId);
    }),
  ],
})

Observe that every app start triggers a full sync:

LOG modelSynced: {"isFullSync":true,"isDeltaSync":false,"counts":{"new":0,"updated":21,"deleted":0}}

Confirm that local DataStore already contains the same data as observed in the sync event.

Disable cloud sync and inspect the local database; the same records that are being "updated" on each start are already present.

Code Snippet

My schema in my amplify backend

type Card @model @auth(rules: [{ allow: owner, ownerField: "userId" }]) {
  userId: String! @primaryKey(sortKeyFields: ["saveId"])
  saveId: String!
  count: Int
  prevCount: Int
  favorite: Boolean
  lastUpdatedBy: String
}

How I persist the data locally:

persistEntry: async (update: CardUpdate): Promise<Card | null> => {
  try {
    const original = await DataStore.query(Card, (c) => c.saveId.eq(update.saveId));
    if (original.length == 0) {
      return await StorageHelper.createEntry(update);
    }
    return await StorageHelper.updateEntry(original[0], update.dynamicCardData);
  } catch (error) {
    console.error('Error saving entry:', error);
    return null;
  }
},

updateEntry: async (originalCard: Card, updatedData: DynamicCardData) => {
  const deviceId = await StorageHelper.getDeviceId();
  const updatedCard = await DataStore.save(
    Card.copyOf(originalCard, (updated) => {
      Object.assign(updated, updatedData);
      updated.lastUpdatedBy = deviceId;
    })
  );
  return updatedCard;
},

createEntry: async (update: CardUpdate): Promise<Card> => {
  const user = await StorageHelper.getLastSignedInUser();
  const deviceId = await StorageHelper.getDeviceId();
  const userId = user?.id ?? 'guest';

  return await DataStore.save(
    new Card({
      userId,
      saveId: update.saveId,
      lastUpdatedBy: deviceId,
      ...update.dynamicCardData,
    })
  );
}

How I setup the listeners. The hook is only called once on app start in my layout.tsx. restartSync is only called on a relogin.

/* eslint-disable @typescript-eslint/no-explicit-any */
import { DataStore } from '@aws-amplify/datastore';
import { Hub } from 'aws-amplify/utils';
import { useEffect, useRef } from 'react';

import { Card } from '@/storage/models';
import StorageHelper from '@/storage/StorageHelper';
import { updateIsCloudSynching } from '@/store/appSlice';
import { syncCloudCardUpdate } from '@/store/galleryThunks';
import { reloadAfterSync } from '@/store/landingThunks';
import { useAppDispatch, useAppSelector } from '@/store/store';
import { AuthMode } from '@/types/auth';

const useCloudSync = () => {
  const dispatch = useAppDispatch();
  const authMode = useAppSelector((state) => state.session.authMode);
  const appReady = useAppSelector((state) => state.app.appReady);
  const deviceIdRef = useRef<string | null>(null);
  const observerRef = useRef<any>(null);
  const hubListenerRef = useRef<any>(null);
  const lastAuthStateRef = useRef<AuthMode | null>(null);

  const onModelSynched = async (data: any) => {
    if (data && typeof data === 'object' && 'isFullSync' in data && 'isDeltaSync' in data) {
      const { isFullSync, isDeltaSync, counts } = data as {
        isFullSync: boolean;
        isDeltaSync: boolean;
        counts: { new: number; updated: number; deleted: number };
      };

      if ((isFullSync || isDeltaSync) && (counts.new > 0 || counts.updated > 0 || counts.deleted > 0)) {
        dispatch(reloadAfterSync());
      }
    }
  };

  const onOutboxStatus = (data: any) => {
    if (data && typeof data === 'object' && 'isEmpty' in data) {
      dispatch(updateIsCloudSynching(!data.isEmpty));
    }
  };

  const onSyncQueriesStarted = (data: any) => {
    dispatch(updateIsCloudSynching(true));
  };

  const onSyncQueriesReady = (data: any) => {
    dispatch(updateIsCloudSynching(false));
  };

  const startHubListener = () => {
    hubListenerRef.current = Hub.listen('datastore', async (hubData) => {
      const { event, data } = hubData.payload;

      if (event === 'modelSynced') onModelSynched(data);
      if (event === 'networkStatus') console.log('networkStatus:', data);
      if (event === 'ready') console.log('DataStore ready:', data);
      if (event === 'outboxStatus') onOutboxStatus(data);
      if (event === 'syncQueriesStarted') onSyncQueriesStarted(data);
      if (event === 'syncQueriesReady') onSyncQueriesReady(data);
    });
  };

  const restartSync = async () => {
    try {
      observerRef.current?.unsubscribe();
      hubListenerRef.current?.();
      hubListenerRef.current = null;

      await DataStore.stop();
      await DataStore.start();

      startHubListener();
    } catch (error) {
      console.error('Error restarting DataStore Sync:', error);
    }
  };

  useEffect(() => {
    if (!appReady) return;
    
    observerRef.current?.unsubscribe();
    observerRef.current = DataStore.observe(Card).subscribe(({ element, opType }) => {
      if (deviceIdRef.current === element.lastUpdatedBy) return;
      dispatch(syncCloudCardUpdate(element, opType));
    });

    if (!hubListenerRef.current) {
      startHubListener();
    }

    return () => {
      observerRef.current?.unsubscribe();
      if (hubListenerRef.current) {
        hubListenerRef.current();
        hubListenerRef.current = null;
      }
    };
  }, [appReady]);

  useEffect(() => {
    if (lastAuthStateRef.current === 'loggedOut' && authMode === 'auth') {
      restartSync();
    }

    lastAuthStateRef.current = authMode;

    if (authMode === 'auth' || authMode === 'guest') {
      const fetchDeviceId = async () => {
        const deviceId = await StorageHelper.getDeviceId();
        deviceIdRef.current = deviceId;
      };
      fetchDeviceId();
    }
  }, [authMode]);
};

export default useCloudSync;

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

Iphone 15 Pro (Simulator)

Mobile Operating System

iOS 17.5

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Feb 16, 2025
@ertan95
Copy link
Author

ertan95 commented Feb 17, 2025

I've noticed that this only happens on the simulator for some reason. When testing the app on a real device, it works as expected. Is it a known issue that the simulator behaves this way?

@chrisbonifacio chrisbonifacio added the DataStore Related to DataStore category label Feb 17, 2025
@ertan95
Copy link
Author

ertan95 commented Feb 19, 2025

Can be closed since it works on real devices

@ertan95 ertan95 closed this as completed Feb 19, 2025
@github-actions github-actions bot removed pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DataStore Related to DataStore category
Projects
None yet
Development

No branches or pull requests

2 participants