GSI (Global Secondary Index) errors with Amplify push

Have you ever used AWS Amplify with AppSync and done the command amplify push only to have it work for about 5 minutes and then fail, with an annoying error, “Attempting to edit the global secondary index”?

It happens sometimes, especially in development when iterating on the design of relationship in your schema. GraphQL is a powerful language for expressing relationships between objects in your data model. This limitation of DynamoDB makes this iteration painful. DynamoDB cannot update, modify, create, or delete more than on GSI (Global Secondary Index) at a time on a particular table.

How Did I Get Here

You probably made multiple changes to your schema. Every time you add a line like @key(name: "keyName", field: ["objectId"]) you’re ultimately causing Amplify to add a GSI to your DynamoDB Table. Add more than one between calls to amplify push and you’re going to encounter this error.

The Solution: Easy Part

The first thing to do is to log into the AWS Console, navigate to DynamoDB, and examine your table and it’s indexes. amplify push will complete BEFORE the indexes are finished being created in DynamoDB. If you try to push a second time before DynamoDB is finished, you’ll get an error.

Look for any line that says “Creating”. If it’s there, you just have to wait. Click the refresh button periodically to find out if it’s finished yet. Then try amplify push again.

The Harder, But Sure Fix

Some folks recommend amplify remove api and then amplify add api as a way to fix this problem. That is a BIG hammer. For one thing, it will DELETE ALL YOUR DATA. For another, it’s not needed. And also, if you have inter-dependencies, the remove call might fail, for example if you have an amplify function connected to Cognito as a post-user-sign-up hook.

Step 1: Find the CloudFormation Template for each Table

Use AWS Console to navigate to CloudFormation. Find the top level cloud formation stack for your environment. Click on that and look at its resources until you find api. Then click on that until you find the templates for each of the Object types in your schema. We’ll need to go through the following steps for *each* of those.

Step 2: Download the Current Template

I simply copied the current template onto my clipboard, and then saved it as a new file:

X=Segment
pbpaste > cur_$X.json

Use YOUR object name, not Segment. Forgive me, I am a Command Line geek. Use whatever tools you want, e.g. create a new file in a text editor, paste it, and then use your favorite file comparison tool for the next step.

Step 3: Compare AND EDIT the Template

I’m a vim user so this was all I had to do to compare them

vim -d cur_$X.json amplify/backend/api/appname/build/stacks/$X.json

Use YOUR appname, not appname.

Look at the section GlobalSecondaryIndexes. Are there more than 1 difference between the current template and the new one the Amplify want’s to create? Edit the downloaded JSON, bringing over only one of the changes, so that there’s only one difference. We’ll be adding those over one at a time, deploying the change after each edit. Yes, I know, this is tedious. But it’s better than destroying your entire environment and losing all the data.

Step 4: Update the Template in CloudFormation

Now go to the template for that Object. Click Update from the Template tab. It will say “It is recommended to update through the root stack“. Choose “Update nested stack”.

The screen that recommends you update the root stack.

Select “Replace current template” and “Upload a template file”

The Screen you see when going to update a CloudFormation Template

and upload your edited JSON file

Select your Edited CloudFormation JSON template

Then click Next a bunch

Updating a CloudFormation Template requires you answer a bunch of questions. Fortunately it has the right answers already selected.

You will have to give it permission to do something with IAM. Click the box.

A scary warning that CloudFormation is going to update IAM

With luck, it will succeed

When the CloudFormation template is properly updated, you’ll see the words “UPDATE_COMPLETE”. If it failed, you’ll see “UPDATE_ROLLBACK_COMPLETE” in red.

Step 5: Goto Step 3

If you had to remove more than one GSI change, add it back — again one-at-a-time and repeat starting at Step 3.

Step 6: Goto Step 2

Repeat this for all your Objects (which each has their own Table).

Step 7: Amplify Push Will Work!

Now that the GSIs have all been created, amplify push will be successful!

? Generated GraphQL operations successfully and saved at src/graphql
? Code generated successfully and saved in file src/API.ts
? All resources are updated in the cloud
GraphQL endpoint: https://<redacted>.appsync-api.us-east-1.amazonaws.com/graphql

take(1) can be dangerous

When putting together reactive networks using Reactive Extensions (e.g. RxJS in Javascript / Typescript), I often need logic that responds to a single event. `take(1)` is useful in this situation.

However, more than once, `take(1)` is a cursed operator, because it can easily cause an RxNetwork *to go dead*. This is because after a single event it completes.

It’d behoove one to remember this and make sure that’s really what you want. In this case, I was doing a `filter()` after a `take(1)` and it resulted in a reactive stream that send no events and just completed. I really needed to put the `filter` first.

TIL: git show <commit>:path/file.txt

Have you ever asked yourself, “How do I show a file at a particular commit or revision in its entirety?” “How do I print out a file not a diff in git?” “Show me a git file at a commit on stdout!” Here’s how you do it.

I knew about git show <commit> which shows a diff of all the files changed in a commit. You can even scope it with git show <commit> path/file.txt to show the diff for a particular file. Today I learned if you use a : (colon) instead of a (space) between the commit id and the file path git will instead show you the file in its entirety at that revision.

$ cd temp/
$ mkdir git_show_example
$ cd git_show_example/
$ git init
Initialized empty Git repository in .../temp/git_show_example/.git/
$ commit --allow-empty -m 'Initial commit'
[master (root-commit) 2f2f99d] Initial commit
$ echo 'Hello, World' > file.txt
$ git add file.txt
$ git commit -m 'Hello world'
[master 4bc4156] Hello world
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt
$ echo 'Goodbye' > file.txt
$ git commit -a -m 'Goodbye'
[master 8af3b90] Goodbye
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git show 4bc4156:file.txt
Hello, World

AWS ElasticBeanstalk

“any attached Amazon RDS DB instance will be deleted”. This boldfaced warning is presented when you sometimes need to resort to last measures to get an EB instance back on its feet. Lest you panic, you can easily see if your EB instance is managing any RDS instances by looking at the Database section of the configuration. It may well be you don’t have any databases at all — but they present this error message regardless of whether you do or do not have any.

Untitled Poem by Frances Rahn

I have lost my two babies – a boy and a girl.

And all that is left is a bootie and a curl.

Oh, the years seem like sixty instead of sixteen

Since I ruled their kingdom as nursery queen.

Dearest prince, and sweet princess, I long to retain

The vision of you in your childhood domain

Where a baby’s low coo, and a toddler’s soft kiss

Were loaned by the Master of heavenly bliss.

Though the princess stayed only from Spring until Fall

Yet there isn’t a soul who will ever know all

That she took of my heart when, without any fright,

She closed her blue eyes and slipped out in the night.

But the prince is a man now – that is, he’s almost

A man, and a fine one, I’m eager to boast.

I have done what I’m able since he was a lad

To help him distinguish the good from the bad.

Though I’m proud of these seventeen years in review,

Some times, like tonight, how I wish he were two.

How I yearn like a beggar on horseback to ride

On back through the years with them on my side,

Little dimple-kneed princess and apple-cheeked prince,

Much sweeter that anything I have known since.

                                   —-F.W.R.

                                        2/15/54

JSTimers is not a registered callable module

This can happen if you have two versions of react-native installed. I use yarn to install dependencies, and normally fix this by adding a resolutions section to my package.json:

{
...
  "dependencies": {
...
    "react": "16.9.0",
    "react-native": "0.61.5",
...
  },
...
  "resolutions": {
    "@types/react": "16.9.0",
    "react": "16.9.0",
    "react-native": "0.61.5"
  },
  "devDependencies": {
...
    "@types/react": "^16.9.0",
...
  },
...
}

Make certain that the versions are consistent — otherwise multiple versions may end up installed by react and some of those versions will not be able to connect to the native dependencies, resulting in an error like JSTimers is not a registered callable module or Module RCTDeviceEventEmitter is not a registered callable module.

Working Goldfish

Our goldfish have returned indoors after another successful summer of eating mosquito larvae in the water lily pond (a 25 gallon plastic tub). I overwinter the water lily bulb in a 5-gallon bucket that’s loosely covered in the basement.  Then in March I fill the outdoor pond and put the lily in the bottom.  I wait until I see a few larvae swimming in the pond before I put the goldfish out. That first day they gorge themselves on all the larvae.  We’ve had these two 5-cent comet goldfish for 10 years now. They absolutely thrive in the bucket all summer, and nary a mosquito larvae survives while they’re on duty. I don’t feed them at all — enough bugs and leaves and algae and who knows what comes along that they are fat as can be all summer. Every November I chip them out from beneath the frozen water and bring them inside.  Let them temperature acclimate in their own pond water in a bucket inside for 24 hours.  Then carefully net them into the aquarium that’s been running empty all summer. They live inside all winter — we do feed them now of course — no random bugs dropping in while inside (I HOPE).

Their names are Harold and Henry.

React-Native Viewpager

Android has a view called a “viewpager”. It’s sort of like iOS PageViewController. There are several efforts to bring this to react-native as a cross-platform solution. rn-viewpager is now abandoned, even though it worked great. react-native-app-intro-slider is interesting, but seems special purpose for doing welcome screens — it doesn’t allow other interesting use cases because it seems to assume it’s always the full width of the device window. react-native-viewpager is another one but it’s Android only.

One that seems often overlooked is good old tabview. This can be customized so that the tabs themselves look like paging dots and it already has a nice “viewpager” style animation.

So next time you’re googling for react-native viewpager, maybe consider tabview as an easy solution. It’s well supported by the community and doesn’t have as big a risk of being orphaned like rn-viewpager was.